React相关
React 是一个采用声明式,高效而且灵活的用来构建用户界面的框架。
JSX
本质上来讲,JSX 只是为React.createElement(component, props, ...children)
方法提供的语法糖。比如下面的代码:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
编译为:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()
这个方法首先会进行一些避免bug的检查,之后会返回一个类似下面例子的对象:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};
这样的对象被称为React元素
,它代表所有你在屏幕上看到的东西。
我们用 React 开发应用时一般只会定义一个根节点。要将 React 元素渲染到根DOM节点中,我们通过把它们都传递给ReactDOM.render()
的方法来将其渲染到页面上:
ReactDOM.render(
element,
document.getElementById('root')
);
每当 React 元素发生变化时,ReactDOM
首先会比较元素内容先后的不同,然后操作浏览器DOM更新改变了的部分。
组件 & Props
当 React 遇到的元素是用户自定义的组件,它会将 JSX 属性作为单个对象传递给该组件,这个对象称之为props
。无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props 。
例如,这段代码会在页面上渲染出Hello,Sara
:
//使用 ES6 class 来定义一个组件,组件名称必须以大写字母开头。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
我们来回顾一下在这个例子中发生了什么:
- 我们对
<Welcome name="Sara" />
元素调用了ReactDOM.render()
方法。 - React 将
{name: 'Sara'}
作为props
传入并调用 Welcome 组件。 - Welcome 组件将
<h1>Hello, Sara</h1>
元素作为结果返回。 - ReactDOM 将DOM更新为
<h1>Hello, Sara</h1>
。
State & 生命周期
组件的通过props
获取属性,且其不能修改;当我们需要修改当前组件的状态时,要用到state
来设置局部状态,需要通过this.setState()
来更新组件局部状态:
class Toggle extends React.Component {
constructor(props) {
super(props); //初始化this,并赋值this.props
this.state = {isToggleOn: true}; //初始化this.state
this.handleClick = this.handleClick.bind(this); //为this.handleClick绑定this对象
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
})); //用this.setState()更新this.state
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
每一个组件都有几个你可以重写以让代码在处理环节的特定时期运行的“生命周期方法”。方法中带有前缀will
的在特定环节之前被调用,而带有前缀did
的方法则会在特定环节之后被调用。
-
装配:这些方法会在组件实例被创建和插入DOM中时被调用:
- constructor(`props`)
- componentWillMount()
- render()
- componentDidMount() -
更新:属性或状态的改变会触发一次更新。当一个组件在被重渲时,这些方法将会被调用:
- componentWillReceiveProps(`nextProps`)
- shouldComponentUpdate(`nextProps`, `nextState`)
- componentWillUpdate(`nextProps`, `nextState`)
- render()
- componentDidUpdate(`prevProps`, `prevState`) -
卸载:当一个组件被从DOM中移除时,该方法被调用:
- componentWillUnmount()
当项目视图交互复杂且频繁的时候,依旧采用 state 进行状态更改会显得异常繁琐和不可预测。
这时我们就需要借助 Redux 框架,将状态数据全部转交给 Redux 处理,React 专一负责视图显示,这样会让项目逻辑变得简单而清晰。
Redux相关
三大原则:
- 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个
store
中。 - 惟一改变 state 的方法就是触发
action
,action 是一个用于描述事件的普通对象。 - 为了描述 action 如何改变 state tree ,你需要编写
reducers
。
Action
Action 是把数据从项目传到 store 的有效载荷。它是 store 数据的唯一来源。通常你会通过store.dispatch()
将 action 传到 store。
Action 本质上是 JavaScript 普通对象,添加新 todo 任务的 action 是这样的:
{
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
Action 创建函数
就是生成 action 的方法。在 Redux 中的 action 创建函数只是简单的返回一个 action:
function addTodo(text) {
return {
type: 'ADD_TODO',
text: text
}
}
这样做将使 action 创建函数更容易被移植和测试。只需把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程。
dispatch(addTodo(text));
//或者创建一个 被绑定的 action 创建函数 来自动 dispatch:
const boundAddTodo = (text) => dispatch(addTodo(text));
boundAddTodo(text);
store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下你会使用 react-redux 提供的connect()
帮助器来调用。
Reducer
Action 只是描述了有事情发生了这一事实,而reducer
要做的事情正是指明应用如何更新 state 。reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
我们将以指定 state 的初始状态作为开始。Redux 首次执行时,state 为 undefined,此时我们可借机设置并返回应用的初始 state:
const initialState = {}; //初始化state
function todoApp(state = initialState, action) {
switch (action.type) {
case 'ADD_TODO':
return Object.assign({}, state, {
text: action.text
})
default:
return state //在 default 情况下返回旧的 state
}
}
每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。
combineReducers()
所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp;
注意上面的写法和下面完全等价:
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
combineReducers 接收一个对象,可以把所有*的 reducer 放到一个独立的文件中,通过 export 暴露出每个 reducer 函数,然后使用 import * as reducers 得到一个以它们名字作为 key 的 object:
import { combineReducers } from 'redux'
import * as reducers from './reducers' const todoApp = combineReducers(reducers)
Store
action 描述发生了什么,reducers 根据 action 更新 state,Store
就是把它们联系到一起的对象。Store 有以下职责:
- 维持应用的 state;
- 提供
getState()
方法获取 state; - 提供
dispatch(action)
方法更新state; - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener)
返回的函数注销监听器。
我们使用 combineReducers() 将多个 reducer 合并成为一个。现在我们将其导入,并传递 createStore()
。
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
let store = createStore(todoApp, window.STATE_FROM_SERVER);
数据流
Redux 应用中数据的生命周期遵循下面 4 个步骤:
- 调用 store.dispatch(action)。
- Redux store 调用传入的 reducer 函数。
- 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
- Redux store 保存了根 reducer 返回的完整 state 树。
Router相关
直接使用整合后的react-router-redux
,后面抽时间再详细讲一下,具体使用的话模仿官方案例吧,官方文档。
容器组件 和 展示组件
Redux 的 React 绑定库包含了 容器组件和展示组件相分离 的开发思想。
明智的做法是只在最顶层组件(如路由操作)里使用 Redux。其余内部组件仅仅是展示性的,所有数据都通过 props 传入。
系列目录
- 前端大统一时代即将来临?
- React项目实战:环境搭建
- React项目实战:react-redux-router基本原理
- React项目实战:登录页面(编辑中)