背景
在Redux出现之前,在构建复杂任务时管理状态是相当痛苦的事情。受Flux应用程序设计模式的启发,Redux设计用于管理Java应用程序中的数据状态。虽然它主要用于React,但是可以使用Redux与不同的框架和库(如jQuery,Angular或Vue)。
Redux确保您的应用程序的每个组件都可以直接访问应用程序的状态,而不必将props发送到子组件,或使用回调函数将数据发送回父组件。
在这篇文章中,我将和大家讨论Redux,它如何根植于函数式编程的概念以及如何决定在应用程序中是否需要它。
什么是Redux
官方版: Redux是一个流行的JavaScript框架,为应用程序提供一个可预测的状态容器。Redux基于简化版本的Flux框架,Flux是Facebook开发的一个框架。在标准的MVC框架中,数据可以在UI组件和存储之间双向流动,而Redux严格限制了数据只能在一个方向上流动.如下图:
为什么我们需要Redux?
组件没有它们的状态吗?为什么你需要一个工具来帮助你管理这个状态呢?不要误会我的意思;React是一个比较出色的框架。可以用一个框架来编写一个完整的应用程序。但是随着应用程序变得越来越复杂,越来越多的组件使用一个框架来管理这个可能变得非常棘手。
这是Redux为你节省一天的地方;它减轻了这种应用中出现的复杂性。如果你有一些React的经验,你会知道React的数据流是这样的,父组件传递道具到子组件。在一个庞大的应用程序中,数据通过状态和props流经很多组件,通信往往容易出错。相信我,你的代码将变得难以阅读甚至很难改进。
再看看下面的图表,你就知道我在说什么:
在React(以及其他框架)中,不鼓励两个不具有父子级关系的组件之间的通信。 React建议,如果你必须这样做,你可以按照Flux的模式建立你的全局事件系统 - 这就是需要Redux的地方。
借助Redux,您可以在store中保留所有应用程序状态。如果组件A中发生状态更改,则会将其中继成到store,并且需要注意组件A中状态更改的其他组件B和C可以读取store数据:
看到了吧?这比我们想象的要好得多。如果我们让组件相互通信,我们会创建一个容易出错和不可读的代码库,但有Redux加入就不同了。组件A将其状态更改发送到store,如果组件B和C需要此状态更改,则可以从store中获取它。因此,我们的数据流逻辑是无缝的。
开始前你需要知道的概念
store
store是一个数据仓库,一个应用中store是唯一的,它里面封装了state状态,当用户想访问state的时候,只能通过store.getState来取得state对象,而取得的对象是一个store的快照,这样就把store对象保护起来。
import React from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import mySaga from './saga.js';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(todos, applyMiddleware(logger, sagaMiddleware));
sagaMiddleware.run(mySaga);
function App() {
return (
<Provider store = {store}>
<div className="App">
</div>
</Provider>
);
}
export default App;
Actions
action描述了一个更新state的动作,它是一个对象,其中type属性是必须有的,它指定了某动作和要修改的值。让我们看看工作中最基本的一个action例子:
Reducer
reducer是更新state的核心,它里面封装了更新state的逻辑,reducer由外界提供(封装业务逻辑,在createStore时传入),并传入旧state对象和action,将新值更新到旧的state对象上返回。例如:
const todos = (state = newstate, action) => {
switch (action.type) {
case 'ADD_TODO':
newstate.num = newstate.num + 1;
return Object.assign({}, state, {
num: newstate.num
});
case 'REDUCE_TODO':
if (newstate.num > 0) {
newstate.num = newstate.num - 1;
}
return Object.assign({}, state, {
num: newstate.num
});
default:
return state;
}
}
在构建更复杂的应用程序时,建议使用Redux的combineReducers()方法。此方法将应用程序中的所有reducer合并到一个reducer列表中,其中每个reducer处理其应用程序状态的一部分,每个reducer对应一个与其同名的state对象。在开发大型过程中每个页面应该有自己的reducer。将state进行分割。避免不分割导致state过大引起的页面性能问题。combineReducers使用如下:
dispatch
dispatch是一个方法,它用于派发一个动作action,这是唯一的一个能够修改state的方法,它内部会调用reducer来调用不同的逻辑基于旧的state来更新出一个新的state。
有了这些基础,开始我们的第一个简易react-redux购物车
react和redux的安装
1.使用react脚手架create-react-app创建一个react项目
npm install -g create-react-app
项目目录结构如下:
2.安装redux和react-redux
npm install --save react-redux
npm install --save redux
3.在React中使用Redux
(1)首先构造界面,引入子组件,react,redux,createstore,provider
在根组件中app.js中引入store对象,它是所有组件的容器,因此它要做所有组件的state提供者的角色,所以它的任务要把state提供给所有子组件使用,这就需要react-redux包提供的一个组件:Provider:Provider也是一个组件,它只有一个属性:store,传入创建好的store对象即可.代码如下:
import React from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import mySaga from './saga.js';
import todos from './reducer/reducer';
import Con1 from './components/con1/con1';
import Con2 from './components/con2/con2';
import Con3 from './components/con3/con3';
import SagaTest from './components/saga/saga'
import './App.css';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(todos, applyMiddleware(logger, sagaMiddleware));
sagaMiddleware.run(mySaga);
function App() {
return (
<Provider store = {store}>
<div className="App">
<Con3/>
<Con1/>
<Con2/>
<SagaTest/>
</div>
</Provider>
);
}
export default App;
这样就意味着Provider包裹的所有组件都可合法的取到store。
现在数据已经提供,还需要子组件来接收,同样接收store数据react-redux包也为我们提供了一个方法:connect。
connect这个方法非常奇妙,它的功能非常强大,它可以把仓库中state数据注入到组件的属性(this.props)中,这样子组件就可以通过属性的方式拿到仓库中的数据。下面使用connect方法将state数据注入到子组件con1中:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../../action/action';
import './con1.scss';
class Con1 extends Component {
constructor (props) {
super(props);
this.handleClickAdd = this.handleClickAdd.bind(this);
}
handleClickAdd() {
this.props.dispatch(addTodo());
}
render () {
return (
<div className="con1" onClick={this.handleClickAdd}>
+
</div>
)
}
}
const mapStateToProps = (state, ownProps) => ({
state
});
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatch
});
const Con11= connect(
mapStateToProps,
mapDispatchToProps
)(Con1);
export default Con11;
可以看到它的写法很怪,connect是一个高阶函数(函数返回函数),它的最终返回值是一个组件,这个组件(con1)最终“连接”好了顶层组件Provider提供的store数据。
connect的第一个参数是一个函数,它的返回是一个对象,返回的对象会绑定到目标组件的属性上,函数参数state就是store.getState的返回值,使用它就可以取到所有state上的数据。mapStateToProps有两个参数,参数可以为空。当有参数时,组建订阅参数,参数改变将导致组建重新渲染。ownprops是这个组建从父组件接受到的数据。高阶函数传入的参数就是要注入的组件,这里是con1,这样在con1组件中就可以通过this.props.state.num取到待办事项的数据。
这样就可以编写好我们的第一个统计功能(点击按钮num加1),点击按钮触发事件。通过dispatch函数将action传给reducer。reducer收到action后对旧的state做相应的处理生成新的state.action定义了要完成何种操作,比如加减。reducer是一个函数,根据dispatch给他的action的type去判断要怎么样处理state.返回新的state.action和reducer文件的代码如下:
//加1
const addTodo = () => ({
type: 'ADD_TODO'
});
//减1
const reduceTodo = () => ({
type: 'REDUCE_TODO'
});
});
export {
addTodo,
reduceTodo
}
//reducer
let newstate = {
num: 0,
data: {}
};
const todos = (state = newstate, action) => {
switch (action.type) {
case 'ADD_TODO':
newstate.num = newstate.num + 1;
return Object.assign({}, state, {
num: newstate.num
});
case 'REDUCE_TODO':
if (newstate.num > 0) {
newstate.num = newstate.num - 1;
}
return Object.assign({}, state, {
num: newstate.num
});
default:
return state;
}
}
项目源码:https://github.com/zhou111222/car
至此,一个简易的购物车就完成了,截图如下: