单项数据类框架始祖Flux
Flux的目的是用来替代Backbone.js、Ember.js等MVC框架。
MVC框架将应用分为三个部分:
Model(模型) 负责管理数据,大部分业务逻辑放在Model中;
View(视图) 负责渲染用户界面,避免在View中涉及业务逻辑;
Controller(控制器) 负责接受用户的输入,根据用户的输入调用对应的Model部分的逻辑,把产生的数据交给View部分,让View渲染出必要的输出;
MVC框架的缺点
MVC框架提出的数据流很理想,但是在实际框架视线中,总是允许View和Model直接通信,从而出现以下情况。
在MVC框架中,系统能提供什么的服务,通过Controller 暴漏函数来实现。每增加一个功能,Controller就要增加一个函数。
Flux包含四个部分:
Dispatcher 处理动作分发,维持Store之间的依赖关系;
Store 负责存储数据和处理数据逻辑;
Action 驱动 Dispatcher 的 JavaScript 对象;
View 视图部分,负责显示用户界面;
在Flux中 Dispatcher 始终只需要暴漏一个函数Dispatch,当需要增加新的功能时,只用增加一种新的Action类型,Dispatcher 对外接口不改变。
Redux的基本原则
Flux的基本原则是“单向数据流”,Redux在此强调三个基本原则
1.唯一数据源
应用的状态应该只存储在唯一一个Store上,这个唯一Store上的状态,是一个树形的对象,每个组件往往只是树形象上的一部分数据,如何设计Store上状态的结构,就死Redux应用的核心问题。
2.保持状态只读
不能直接修改状态,要修改Store的状态,必须通过派发 action 对象完成。
3.数据改变只能通过纯函数完成
纯函数就是Reducer,reducer接受两个参数,第一个参数state是当前的状态,第二个参数action是接受到的action对象。
Action 是把数据从应用(译者注:这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说会通过store.dispatch() 将 action 传到 store。
约定:action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。除了 type 字段外,action 对象的结构完全由你自己决定。
Action 创建函数
Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。在 Redux 中的 action 创建函数只是简单的返回一个 action:
function add(text) {
return {
type: TYPE_NAME,
text
}
}
Redux 中只需把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程
dispatch(addTodo(text))
Reducers 指定了应用状态的变化如何响应actions并发送到 store 的,actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
reducer规范:(不能做)
修改传入参数;
执行有副作用的操作,如 API 请求和路由跳转;
调用非纯函数,如 Date.now() 或 Math.random()。
Store 就是把action、reducer联系到一起的对象。Store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
注意 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合而不是创建多个 store。
详细使用方法请看Redux中文文档
容器组件和展示组件
承担第一个任务的组件,也就是负责和 Redux Store 打交道的组件,处于外层,被称为容器组件;对于承担第二个任务的组件,也就是专心负责渲染界面的组件,处于内层,叫做展示组件。
展示组件是一个纯函数,根据 props 产生结果。
组件Context
在一个应用中,不同组件都会调用全局的Store,在组件中直接导入Store不利于组件复用,应该只在最顶层调用一次Store,将Store从上层组件传递给子组件,如果用props传递,所有的组件props都要增加对这个支持,很麻烦。React提供了一个Content的功能,能解决这个问题。
Context(上下文环境),让一个树状组件上所有组件都能访问一个共同的对象,需要上级组件和下级组件配合。
//定义在src文件夹下,是一个通用的 context 提供者
import React, {Component} from 'react'
import PropTypes from 'prop-types'
class Provider extends Component {
getChildContext() {
//返回的代表Context的对象
return {
store: this.props.store
};
}
render() {
//简单的把子组件渲染出来代表Provider标签之间的组件
return this.props.children;
}
}
//为了Provide比较通用,所以store从外部传递进来
//为了让Provider能够被React认可为一个Context提供者,需要指定Provider的childContextTypes
Provider.childContextTypes = {
store: PropTypes.object
};
export default Provider;
每个React组件都有一个特殊属性 children,代表的是子组件。
子组件中对context的使用
className.contextTypes = {
store: PropTypes.object
}
在子组件中对store的访问通过 this.context.store 完成。由于自定义了构造函数,所以构造函数要添加 context 参数
constructor(props, context) {
super(props, context);
//es6 super(...arguments)
}
context的使用:必须谨慎使用,只有对那些每个组件都可能使用,但是中间组件又不可能使用的对象才必须使用Context。
具体使用方法请参考聊一聊我对 React Context 的理解以及应用-张国钰
后记 将组件拆分为容器组件和展示组件,以及利用 React 的 Context 提供一个所有组件都可以直接访问的 Context,这两种方法都有自己的套路来改进 React 应用,可以将这两种方法的套路部分提取出来,已经有一个库实现,就是 react-redux