目前React+redux的技术架构已经应用较为普遍了,博主也是在听过大神建议后直接在react项目中上了redux,由于经验问题,在使用2个月左右后才渐渐开始明白redux的工作过程,本文将从redux源码的角度进行学习,使用的例子在wantplus,文本旨在带领初学者进行redux的流程解析与备忘,如有错误还请指出。
本文的参考文档主要是:redux中文文档 ,以及百度谷歌,有能力的话推荐阅读redux英文文档
在阅读本文之前请确认你学习过React并进行过实践!
ps:本文使用的依赖版本:redux_3.5.2 ; redux-thunk_2.1.0 ; react-redux_4.4.5
ps2:本文内容中使用较多的函数式编程方法,如有问题请问度娘和谷哥
ps3:demo地址–>reduxDemo
1 redux是什么
Redux 是管理state(状态)的一个框架,由于react通常使用组件props属性来进行状态(或数据)的传递,以state属性进行状态(或数据)的存储,在组件多了以后这个传递的过程就会变得十分复杂,而redux就将这个传递过程和状态数据统一管理起来,构成了一个非常清晰的数据流转过程。
2 redux怎么工作的
2.1 react组件的原生状态传递
首先我们看一看react原始的props使用方法,示例代码如下:
//Test组件
import React, { Component } from 'react'
import { render } from 'react-dom'
import TestChild from './testChild'
class Test extends Component {
click(){
this.state = '233';
console.log(this);
}
render() {
return(
<div>
//子组件的属性由此传入,act是一个prop
<TestChild act={this.click.bind(this)}/>
</div>
)
}
}
export default Test;
----------
//TestChild 组件
import React, { Component } from 'react'
import { render } from 'react-dom'
class TestChild extends Component {
render() {
return(
//获取父组件传来的prop
<div onClick={this.props.act}>点我!</div>
)
}
}
export default TestChild;
如代码所写,TestChild组件是Test组件的子组件,Test组件调用了TestChild,并以props的方式将click方法传入了TestChild组件,TestChild在点击后打印出了父对象,如下图,可以看到父对象的state也如click方法所写的设置成为了‘233’。
就这个例子来看react的这种传递属性和保存状态的方式并没有什么问题,但是当项目扩大到一定程度时,比如组件的层次结构变得很深的时候,属性就要经过多次props的传递才能到达目标组件,当props改变的时候我们就必须对多个组件进行修改,同时跨组件的props调用会变得很困难;并且state的控制方法也分散在了各个组件之中,这使得管理state会非常困难。
于是乎,统一管理props和state的方法变得迫切起来,这就是redux所要做的事情。
2.2 redux的结构
首先介绍一下redux的几个核心概念:
Action:能够操作redux数据的唯一方式,本质是一个js对象,这个对象约定须使用一个字符串类型的 type 字段来表示执行的操作。我们使用store.dispatch(action)方法将action对象分发到reducer。
Reducer:根据Action中的type字段来确定如何更新state,本质是个函数,接收旧的 state 和 action,返回新的state。
Store:Store是redux用于存储state的对象,Store也能够提供将Action分发到reducerd中的方法以修改state值。
概念先了解一下就好,接下来我们看看redux的工作方式:
我们用一个简单的显示输入值的程序作为例子来讲解这个流程,本例的项目结构如下图:
运行时效果如下:
点击按钮可以在页面上显示输入框的值:
很简单对吧?那么我们就来看看这是如何用redux实现的吧。
2.2.1 Store
前面说过,redux项目的所有的状态由store进行管理,并且一个App推荐只有一个store对象,因此,我们要在入口文件中调用createStore方法创建store对象并带入需要的组件中。
//项目入口文件entry.js
import { Router, Route} from 'react-router'
import { createStore} from 'redux'
import {Component} from 'react'
import Test from './component/test'
import TestReducer from './reducers/reducer'
//这里建立了store
const store =createStore(TestReducer);
let Main = React.createClass({
render() {
return (
<Test store={store}/>
);
}
});
ReactDOM.render(<Main />,document.getElementById('content'));
接下来,我们进入createStore方法的源码来看看store到底是个什么东西,在安装完redux后,进入redux依赖包中src/createStore.js文件(只展示与项目有关的部分):
//createStore.js
//reducer:改变state的函数,必需
//preloadedState:初始状态,可选
//enhancer:增强createStore的函数,可以在createStore方法上加上用户自定义的功能,可选
export default function createStore(reducer, preloadedState, enhancer) {
//这里可以看出初始状态不是必需的,用户没有设定默认为undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
//这里使用用户传入的enhancer将createStore方法重新包装一下
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
...
//定义内部变量,设置reducer
var currentReducer = reducer
//这里设定的就是所有应用的state
var currentState = preloadedState
...
//获取store中state的方法
function getState() {
return currentState
}
...
//将action发送到reducer中
function dispatch(action) {
...
try {
isDispatching = true
//将当前的action送入reduce中,得到新的state,这是唯一改变state的方法
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
...
return action
}
...
//最终返回的store对象
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
可以看到,createStore必要的参数是reducer,另外两个参数可选,最后返回的store对象包含了4个方法,这里我们只用到dispatch和getState方法。
getState用于获取当前store中的state,而dispatch用于将action传入到用户指定的reducer中。
到这里,我们创建了一个对象,并把它放在了需要使用的组件的prop中往下进行传递。
2.2.2 Reducer
现在我们建立好了store对象,接下来我们看看建立store时的reducer到底是什么吧。
reducer的本质是一个方法,参数是初始state对象和action对象,它来根据传入的action对象的type属性来修改store中的state值,最后返回一个新的state对象,我们从createStore方法中了解到修改state的方法只能是由reducer返回一个新的state。
//reducer.js
//初始state对象
const initState = {
data:""
}
//reducer方法本体
export default function signIn(state = initState,action){
//对action对象的type属性进行判断,针对不同action返回的state也可以不同
switch (action.type){
case 'ACTION':
return Object.assign({}, state, {
//将action对象中的自定义数据取出并放入state
data:action.data
})
default:
return state
}
}
以上就是一个简单的reducer方法,返回值使用assign方法将原有的state和新的state进行整合而不是替代,避免了state覆盖导致的部分数据丢失,推荐使用这种方法。
在建立reducer方法时请不要在方法中放入任何业务逻辑,以免造成日后维护的困难。
2.2.3 Action
action本质上是一个对象,且其中应该包含一个type属性,以保证reducer能够以此来决定如何修改state值。我们也可以在action对象中添加自定义的各种属性,用于传递数据。
我们使用方法来返回action对象,这样可以将业务逻辑放在Action方法中,方便日后维护。以下是本例的action文件:
//action.js
export function testAction(text){
console.log(text);
return {
type:'ACTION',
//自定义数据
data:text
};
}
这里的action方法也很简单,执行一个打印参数的操作,之后返回一个action对象,type属性是必须的,一般来说是一个字符串常量,表示进行的操作,其他的可以自由设置,本例中将text参数放入了data属性中。
2.2.4 组件
组件是react应用的基本组成部分,本例中一共有两个组件:
//父组件Test.js
import React, { Component } from 'react'
import { render } from 'react-dom'
import { connect } from 'react-redux'
import TestChild from './testChild'
import {testAction} from '../action/action'
class Test extends Component {
render() {
const { store} = this.props
return(
<div>
<TestChild act={text=>store.dispatch(testAction(text))} store={store}/>
</div>
)
}
}
export default Test;
----------
//子组件TestChild.js
import React, { Component } from 'react'
import { render } from 'react-dom'
class TestChild extends Component {
click(){
//获取输入框的值
let text =this.refs.input.value;
//调用action方法,同时也调用了dispatch方法,修改了state
this.props.act(text);
//获取state对象
let result = this.props.store.getState();
//从state对象中取值显示到页面
this.refs.p.textContent = result.data ;
}
render() {
return(
<div>
<input type='text' ref= "input"/>
<button onClick={this.click.bind(this)}>点我!</button>
<br/>
<p>state中data的值为:</p>
<p ref="p"></p>
</div>
)
}
}
export default TestChild;
这两个组件的功能就是在点击按钮之后能将输入框中的值显示到页面上,父组件获取到store对象后,将dispatch方法和store对象传入子组件,子组件由点击事件触发dispatch方法的调用,同时调用了action方法。
我们在createStroe方法中已经看到,只要调用了dispatch方法就会触发reducer方法,然后改变state的值,然后我们在子组件中调用store的getState方法获取state值,就能够得到我们传入的input框的输入值了。
流程的基本组成部分到这里就讲解完毕了,这个例子对比上本文最开始的react props方式传值来说是有点复杂,那么我们再将这个流程梳理一遍:
首先在入口文件entry.js调用createStore创建整个App唯一的store —-> 组件点击按钮调用传入的dispatch(action())方法 —-> 触发reducer方法,state改变 —-> 组件由getState方法获取state值进行其他操作
3 总结一下
redux这样做的好处是显而易见的:因为改变state的方式只有将action方法dispatch到reducer方法中这一种,因此我们在写应用时不必再去考虑数据传递的路径问题,在项目规模扩大后优势会变得越来越明显,同时,统一的state管理方式和流程对于刚接手项目的新人来说也剩下了大量熟悉项目的时间。
在理解了redux的工作方式后,我们不难从代码中发现一个问题:
我们将store对象在组件中进行了多次传递,dispatch方法的传递也比较繁琐,有没有解决这个问题的方法呢?
这时候就是使用新工具react-redux的时候了,请关注Redux 从源码开始学习(二):react-redux