Redux是什么
首先要分清楚几个容易混淆的概念Redux
、react-redux
、Flux
。
Flux和Redux
Flux
和Redux
是同一个层级的概念,他们都是用于React
状态管理的一套架构模式。Redux
是Flux
的演进,他们有如下的共同点。
- 单向数据流,即数据只能由
store
流向view
,而不能反过来 - 通过
Action
来更新数据
可以看到,两者的设计理念基本一致,都是通过限制数据更新的过程,让视图的更新更加容易预测。
在此基础上,Redux
做了如下改进
- 数据源唯一,
store
全局唯一,状态树共用 - 保持状态只读,更新的时候不是直接修改状态,只能通过
reducer
返回新的状态 - 使用纯函数,状态更新的过程不会引起除了被更新状态之外的其他变化,同样的输入一定带来同样的输出,状态的变化是确定的
这种修改,一定程度上降低了多数据源管理的复杂程度,也为回溯组件状态带来可能,但是设计上比Flux
要难理解一些
Redux和react-redux
react-redux
是在实际React
项目中帮助我们使用Redux
的插件,就是这么简单。
什么时候使用Redux
先看看Redux
的状态管理有何不同,画几个简单的图就能理解了
以上是抽象出来的传统和Redux
简单工作方式。
我们可以看到,在传统方式下,如果是来自更高层级组件的数据源变化,那么数据的流向是非常自然的,通过props
很容易能将父组件的数据传递到子组件。但如果数据源变化改变了子组件的状态,这时候想要引起绿色目标组件的状态变化,数据的传播方向就会从子组件到父组件,当数据流转到引起变化的组件和目标组件的共同父组件的时候,又会回到上一种情况。
一般我们会通过把控制父组件状态的函数通过props
传入子组件,让子组件调用的方式来实现第二种情况。举个例子,页面将控制SET modelVisibal = false
的函数传入Modal
框中,绑定到关闭按钮的onClick
回调上,实现关闭的作用。
在使用Redux的情况下,当数据源变化,会通过容器组件派发任务的形式更新store
,数据再从store
中流入各个组件。
那到底什么时候使用Redux
呢?
在调研学习的时候,这样一句话让我印象深刻
“如果你不知道是否需要Redux,那就是不需要它”
如果使用传统方法能让你hold的住项目的状态管理以及后续的开发维护,OK,Redux
对你来说可能不那么必要,反而使用还会加大工作量。但如果你苦恼于过多的兄弟组件传值,能修改视图的数据源太多太复杂,那么Redux
可能能救你于水火之中。
一言蔽之,一切框架的使用都是为了减少重复工作,让项目往工程化方向演进。
怎么使用Redux
Redux
在项目中的使用需要借助react-redux
(内心OS:终于轮到我出场了!),我们借助官方的TodoList项目的例子给大家讲解一下。
-
第一步,创建一个全局唯一的
store
并绑定到根节点// store.js import { createStore } from "redux"; import rootReducer from "./reducers"; export default createStore(rootReducer); // index.js ... import { Provider } from "react-redux"; ... const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <TodoApp /> </Provider>, rootElement );
在
store.js
,调用createStore
方法创建了一个store
并且export
出去,我们先略过参数rootReducer
,等等再说明。接着在
index.js
中,我们借助react-redux
插件中的Provider
组件,将我们的业务组件包装,并将刚刚创建的store
挂载到上面。 -
第二步,创建
Action
,我们以添加addTodo
为例// action.js ... export const addTodo = content => ({ type: ADD_TODO, payload: { id: ++nextTodoId, content } }); ...
我们创建的
addTodo
是一个箭头函数,参数content
是容器组件触发动作时要传的参数。返回一个会被reducer
处理的对象,type
是一个常量,用于区分不同的Action
;payload
是reducer
更新状态的依据。 -
第三步,创建
reducer
,处理addTodo
// reducers/todos.js ... export default function(state = initialState, action) { switch (action.type) { case ADD_TODO: { const { id, content } = action.payload; return { ...state, allIds: [...state.allIds, id], byIds: { ...state.byIds, [id]: { content, completed: false } } }; } ... } ...
reducer
是我们自己定义来处理Action
的函数,接受的参数有state
(如果不传,就用初始化的默认参数)和action
。当判断
action
的类型是ADD_TODO
时,会从payload
里取出触发Action
时组件传入的参数,返回给store
一个最新的状态。要注意的是,
reducer
不会用直接赋值的方式修改store
或者state
,而是采用return
一个确定的新状态来更新store
。 -
第四步,使用
store
中的状态import React from "react"; import { connect } from "react-redux"; import Todo from "./Todo"; import { getTodosByVisibilityFilter } from "../redux/selectors"; import { VISIBILITY_FILTERS } from "../constants"; const TodoList = ({ todos }) => ( <ul className="todo-list"> {todos && todos.length ? todos.map((todo, index) => { return <Todo key={`todo-${todo.id}`} todo={todo} />; }) : "No todos, yay!"} </ul> ); const mapStateToProps = state => { const { visibilityFilter } = state; const todos = getTodosByVisibilityFilter(state, visibilityFilter); return { todos }; }; // export default TodoList; export default connect(mapStateToProps)(TodoList);
同样,我们需要借助
connect
这个react-redux
中的工具人来帮助我们在组件中使用store
。connect
函数第一个参数,可以对状态树做过滤,mapStateToProps
接受整个状态树,然后过滤出组件需要的状态,返回一个对象,这个对象会作为组件的props
传入组件。connect
函数会返回一个包装函数,我们需要把原来的组件传入其中,组件就能获得store
的使用环境。 -
第五步,触发
Action
改变状态import React from "react"; import { connect } from "react-redux"; import { addTodo } from "../redux/actions"; class AddTodo extends React.Component { constructor(props) { super(props); this.state = { input: "" }; } updateInput = input => { this.setState({ input }); }; handleAddTodo = () => { this.props.addTodo(this.state.input); this.setState({ input: "" }); }; render() { return ( <div> <input onChange={e => this.updateInput(e.target.value)} value={this.state.input} /> <button className="add-todo" onClick={this.handleAddTodo}> Add Todo </button> </div> ); } } export default connect( null, { addTodo } )(AddTodo); // export default AddTodo;
要在组件中使用
Action
,我们双要用到connect
这个函数,这个函数接受的第二个参数,是一个装着action
的对象,这些action
会被包装进组件的props
中。我们就可以在组件中调用addTodo
这个action
。而这些被分发(dispatch
)的action
会被处理。还记得我们第一步略过的
rootReducer
对象吗,它是这样创建的import { combineReducers } from "redux"; import visibilityFilter from "./visibilityFilter"; import todos from "./todos"; export default combineReducers({ todos, visibilityFilter });
这个被
export
出去的就是rootReducer
对象,我们把要注册的reducer
通过combineReducers
方法结合后,注册在store
中,他们就能自动拦截action
,完成数据的处理啦。
小结和一些感想
小结
本文对Redux
做了一个简单的介绍,受限于个人能力,只是最基本的原理和使用方法,只能起到一个抛砖引玉的作用。Redux
还有许多值得深究的地方,比如selector
我只是一笔带过,但其实它也是有个发展历程的。今后如果还有机会,会在React
状态管理这一块做更深入的探讨。
一些感想
刚刚开始学习React
的时候,我从来没有考虑过如何去做数据管理,更没想过框架是如何产生的,只是机械的用别人封装好的方法和框架,只考虑如何实现功能。但是随着逐渐深入,我接触了公司几个不同的业务,我发现他们的数据管理方式各不相同,我开始去接触Redux
、Flux
这些状态管理的框架,在其中也确实得到了技术的锻炼和提升。
最后和大家分享一下男神尤雨溪的知乎回答。
“一直呆在舒适区往往就得不到提升,程序员能力的提升往往都发生在尝试解决一个从没解决过的问题之后进行反思的过程中。”
共勉。