Redux介绍
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。(一个用来管理管理数据状态和UI状态的JavaScript应用工具)其实,有点像vue中的vuex。不过,虽然redux作为react御用状态容器,但实际redux已经做到了对三大框架以及原生JS的支持。随着JavaScript单页应用(SPA)开发日趋复杂,JavaScript需要管理比任何时候都要多的state(状态),Redux就是降低管理难度的。
Redux流程图:
基础入门部分,我们要搞清楚的就是这样一个流程。
流程图中有四个部分(store、actions、reducer和view),他们用一些箭头联系起来。那么接下来我们通过代码演示,让大家对四个抽象概念和其间关系有清晰的认知。
创建Redux中的仓库-store和reducer
Redux工作流程中有四个部分,最重要的就是store这个部分,因为它把所有的数据都放到了store中进行管理。在编写代码的时候,因为重要,所以要优先编写store。
使用Redux之前,我们先安装:npm install --save redux。
安装好redux之后,在src目录下创建一个store文件夹,然后在文件夹下创建一个index.js文件。
index.js就是整个项目的store文件,打开文件,编写下面的代码。
import { createStore } from 'redux' // 引入createStore方法
const store = createStore() // 创建数据存储仓库
export default store //暴露出去
这样,我们的仓库就已经建立好了。但是仓库的内容要更新,我们在管理这些状态时把更新的方法写入index.js显然是不合适的。那么我们需要建立一个新文件reducer.js。这两概念之间的关系可以比作是老板和财务之间的关系,老板手上的原始财务资料是需要加工的,那么在财务加工完之前,老板手中的资料是不会变动的,财务完成工作后把材料交给老板审批。审批过后,老板更新手中的材料。
那么新建完reducer.js后,我们写入以下代码:
const defaultState = {} //默认数据
export default (state = defaultState,action)=>{ //就是一个方法函数
return state
}
接下来,我们把两者联系起来(在store中引入reducer,注意看引入的固定格式):
import { createStore } from 'redux' // 引入createStore方法
import reducer from './reducer'
const store = createStore(reducer) // 创建数据存储仓库,以参数的形式传递给store。
export default store //暴露出去
下面,我们制作一个todoList的案例带大家体会reducer的defaultState有了数据之后如何。
在store中为todoList初始化数据
在reducer.js文件的defaultState对象中添加数据,为什么在reducer里?这里相当于直接是老板已经把材料给财务了。
const defaultState = {
inputValue : '请输入内容',
list:[
'早上4点起床,锻炼身体',
'中午下班游泳一小时'
]
}
export default (state = defaultState,action)=>{
return state
}
组件获得store中的数据
import React, { Component } from 'react';
import 'antd/dist/antd.css'
import { Input , Button , List } from 'antd'
import store from './store'
class App extends React.Component {
constructor(props){
super(props)
this.state=store.getState();
console.log(this.state)
}
render() {
return ();
}
}
通过setState这个API来获得state中的数据。我们来看看控制台中是否打印了数据:
我们给出UI层面的代码:
import React, { Component } from 'react';
import 'antd/dist/antd.css'
import { Input , Button , List } from 'antd'
import store from './store'
class TodoList extends Component {
constructor(props){
super(props)
//关键代码-----------start
this.state=store.getState();
//关键代码-----------end
console.log(this.state)
}
render() {
return (
<div style={{margin:'10px'}}>
<div>
<Input placeholder={this.state.inputValue} style={{ width:'250px', marginRight:'10px'}}/>
<Button type="primary">增加</Button>
</div>
<div style={{margin:'10px',width:'300px'}}>
<List
bordered
//关键代码-----------start
dataSource={this.state.list}
//关键代码-----------end
renderItem={item=>(<List.Item>{item}</List.Item>)}
/>
</div>
</div>
);
}
}
export default TodoList;
Redux Dev Tools的安装与配置
1、建议用Microsoft Edge,进入微软商店直接下载Redux DevTools。
2、Google科学上网下载Redux DevTools。
修改store中的index.js代码:
import { createStore } from 'redux' // 引入createStore方法
import reducer from './reducer'
const store = createStore(reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) // 创建数据存储仓库
export default store //暴露出去
体验Redux的流程
接下来我们通过todoList案例的完善,引入action的角色并完整地展示Redux的流程。
首先,我们为input框添加一个事件,进行以下修改:
接下来,我拿就要创建action了。action就是一个对象,这个对象一般有两个属性,第一个是对action的描述,第二个是要改变的值。
changeInputValue = (e) => {
const action ={
type:'changeInput',
value:e.target.value
}
store.dispatch(action)//传递给store
}
前文已经说了store只是一个仓库,它并没有管理能力,它会把接收到的action自动转发给Reducer。我们现在先直接在Reducer中打印出结果看一下。
这里的reducer接收到了action。现在要作的就是改变store里的值。我们先判断type是不是正确的,如果正确,我们需要从新声明一个变量newState。(记住:Reducer里只能接收state,不能改变state。),所以我们声明了一个新变量,然后再次用return返回回去。
export default (state = defaultState,action)=>{
if(action.type === 'changeInput'){
let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
newState.inputValue = action.value
return newState
}
return state
}
现在store里的数据已经更新了,但是组件还没有进行更新。我们需要打开组件文件App.js,在constructor,写入下面的代码。
效果:
完成了store中数据的更新,那么我们接下来实现todoList的实例。
完善todoList实例
为按钮添加点击事件:
<Button
type="primary"
onClick={this.clickBtn}
>增加</Button>
这时候已经把action传递给了store,然后去Reducer里编写业务逻辑就可以了。
好的,我们来看看效果:
为每一项添加删除事件:
定义reducer中的处理逻辑:
来看效果:
工作技巧
一:
1、把Action Types 单度写入一个文件
写Redux Action的时候,我们写了很多Action的派发,产生了很多Action Types,如果需要Action的地方我们就自己命名一个Type,会出现两个基本问题:
这些Types如果不统一管理,不利于大型项目的服用,设置会长生冗余代码。 因为Action里的Type,一定要和Reducer里的type一一对应在,所以这部分代码或字母写错后,浏览器里并没有明确的报错,这给调试带来了极大的困难。 那我司中会把Action Type单独拆分出一个文件。在src/store文件夹下面,新建立一个actionTypes.js文件,然后把Type集中放到文件中进行管理。
export const CHANGE_INPUT = 'changeInput'
export const ADD_ITEM = 'addItem'
export const DELETE_ITEM = 'deleteItem'
2、引入Action中并使用
写好了ationType.js文件,可以引入到TodoList.js组件当中,引入代码如下:
import { CHANGE_INPUT , ADD_ITEM , DELETE_ITEM } from './store/actionTypes'
引入后可以在下面的代码中进行使用这些常量代替原来的Type值了.
changeInputValue(e){
const action ={
type:CHANGE_INPUT,
value:e.target.value
}
store.dispatch(action)
}
clickBtn(){
const action = { type:ADD_ITEM }
store.dispatch(action)
}
deleteItem(index){
const action = { type:DELETE_ITEM, index}
store.dispatch(action)
}
3、引入Reducer并进行更改
也是先引入actionType.js文件,然后把对应的字符串换成常量,整个代码如下:
import {CHANGE_INPUT,ADD_ITEM,DELETE_ITEM} from './actionTypes'
const defaultState = {
inputValue : 'Write Something',
list:[
'早上4点起床,锻炼身体',
'中午下班游泳一小时'
]
}
export default (state = defaultState,action)=>{
if(action.type === CHANGE_INPUT){
let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
newState.inputValue = action.value
return newState
}
//state值只能传递,不能使用
if(action.type === ADD_ITEM ){ //根据type值,编写业务逻辑
let newState = JSON.parse(JSON.stringify(state))
newState.list.push(newState.inputValue) //push新的内容到列表中去
newState.inputValue = ''
return newState
}
if(action.type === DELETE_ITEM ){ //根据type值,编写业务逻辑
let newState = JSON.parse(JSON.stringify(state))
newState.list.splice(action.index,1) //push新的内容到列表中去
return newState
}
return state
}
这样就实现了复用,比如说增删改查,那查不可能用一次,可能很多地方都会用到,这样我们直接引入文件使用就可以了,可以避免冗余代码。还有就是这样如果我们写错了常量名称,程序会直接在浏览器和控制台报错,可以加快开发效率,减少找错时间。
二:
1、编写actionCreators.js文件
在/src/store文件夹下面,建立一个心的文件actionCreators.js,先在文件中引入上节课编写actionTypes.js文件。
import {CHANGE_INPUT} from './actionTypes'
引入后可以用const声明一个changeInputAction变量,变量是一个箭头函数,代码如下:
import {CHANGE_INPUT} from './actionTypes'
export const changeInputAction = (value)=>({
type:CHANGE_INPUT,
value
})
2、修改todoList中的代码
有了文件,就可以把actionCreatores.js引入到TodoLisit中。
import {changeInputAction} from './store/actionCreatores'
引入后,可以把changeInputValue()方法,修改为下面的样子。
changeInputValue(e){
const action = changeInputAction(e.target.value)
store.dispatch(action)
}
然后再浏览器中打开程序,进行测试,也是完全正常的。
3、修改另两个Action方法
安装上面的例子,修改另两个方法,actionCreatores.js全部代码如下:
import {CHANGE_INPUT , ADD_ITEM,DELETE_ITEM} from './actionTypes'
export const changeInputAction = (value)=>({
type:CHANGE_INPUT,
value
})
export const addItemAction = ()=>({
type:ADD_ITEM
})
export const deleteItemAction = (index)=>({
type:DELETE_ITEM,
index
})
这个文件写完,可以把TodoList.js文件里的所有action都改为直接调用方法的模式。代码如下:
import React, { Component } from 'react';
import 'antd/dist/antd.css'
import { Input , Button , List } from 'antd'
import store from './store'
//关键代码-------------start
import {changeInputAction , addItemAction ,deleteItemAction} from './store/actionCreatores'
//关键代码------------end
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState();
this.changeInputValue= this.changeInputValue.bind(this)
this.storeChange = this.storeChange.bind(this)
this.clickBtn = this.clickBtn.bind(this)
store.subscribe(this.storeChange) //订阅Redux的状态
}
render() {
return (
<div style={{margin:'10px'}}>
<div>
<Input
placeholder={this.state.inputValue}
style={{ width:'250px', marginRight:'10px'}}
onChange={this.changeInputValue}
value={this.state.inputValue}
/>
<Button
type="primary"
onClick={this.clickBtn}
>增加</Button>
</div>
<div style={{margin:'10px',width:'300px'}}>
<List
bordered
dataSource={this.state.list}
renderItem={(item,index)=>(<List.Item onClick={this.deleteItem.bind(this,index)}>{item}</List.Item>)}
/>
</div>
</div>
);
}
storeChange(){
console.log('store changed')
this.setState(store.getState())
}
//--------关键代码------start
changeInputValue(e){
const action = changeInputAction(e.target.value)
store.dispatch(action)
}
clickBtn(){
const action = addItemAction()
store.dispatch(action)
}
deleteItem(index){
const action = deleteItemAction(index)
store.dispatch(action)
}
//--------关键代码------end
}
export default TodoList;
都写完了,我们就可以到浏览器中进行查看了,功能也是完全可以的。这节课我们实现Redux Action和业务逻辑的分离,我觉的这一步在你的实际工作中是完全由必要作的。这样可打打提供程序的可维护性。
总结三点
1、store必须是唯一的,多个store是坚决不允许,只能有一个store空间
2、只有store能改变自己的内容,Reducer不能改变
3、Reducer必须是纯函数