1.什么场景使用redux?
1.项目角度
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(多角色)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了websocket
- view要从多个来源获取数据
2.组件角度
- 某个组件的状态需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
2.redux设计思想
- web应用是一个状态机,视图与状态一一对应的
- 所有的状态,保存在一个对象里面
3.redux三大原则
- 唯一数据源
- 保持只读状态
- 数据改变只能通过纯函数来执行
4.redux流程图示意
5.redux版TodoList(示例原始的redux)
页面示例:
1.抽离默认数据到reducer(定义reducer)
//reducer.js
//1.定义默认的状态数据
const defaultState = {
inputVal: '',
data: [
'尝遍人间甘辛味,言外冷暖我自知',
'一切安乐,无不来自困苦',
'如果脏了还用,就莫不如一开始就用带颜色的。白的就要纯白才行',
'君といると、月が绮丽です',
'得来不易的机会,会让所有的动物去做原来不喜欢做的事',
]
}
// 2.导出一个返回状态数据的函数
// 该函数有两个参数:state/action
// 将defaultState赋值给state,使得state有初始化数据,为后面使用state埋下伏笔
export default (state = defaultState,action) => {
return state;
}
2.使页面上使用state中的数据(即获取state的数据)
创建store以使用
//store/index.js
import { createStore } from 'redux';
import reducer from './reducer';
// 使用createStore 来引用reducer中返回的数据
const store = createStore(reducer);
export default store;
通过store.getState() 赋值给react state,以使state获得数据并显示在页面上。
如果这时想要更改state中的数据呢?
//TodoList.js
import React,{ Component } from 'react';
import { Button,Input,List } from 'antd';
// 引入store
import store from './store';
export default class TodoList extends Component{
constructor(){
super();
// getState获取store中的状态
this.state = store.getState();
}
render(){
return (
<div style={{padding: '40px'}}>
<Input
placeholder="请输入待办事项"
style={{ width: '350px',marginRight: '20px' }}
value={this.state.inputVal}
/>
<Button
type="primary"
>
添加
</Button>
<List
bordered
style={{ marginTop: '30px',width: '435px',userSelect: 'none' }}
dataSource={this.state.data}
renderItem={(item,index) => (
<List.Item>{item}</List.Item>
)}
/>
</div>
)
}
}
3.通过定义action对象变更state中的数据(dispatch:事件派发)
定义action对象以便派发事件
通过store.dispatch(action对象)派发事件
//TodoList.js
import React,{ Component } from 'react';
import { Button,Input,List } from 'antd';
// 引入store
import store from './store';
export default class TodoList extends Component{
constructor(){
super();
// getState获取store中的状态
this.state = store.getState();
}
// 输入框变化
handleChange(e){
// 触发reducer(store)修改数据
const action = {
// type必传
type: 'change_inputVal',
value: e.target.value
};
//dispatch 事件派发
store.dispatch(action);
}
// 新增
handleClick(){
const action = {
type: 'add_to_data'
}
store.dispatch(action);
}
//双击删除
handleDoubleClick(index){
store.dispatch({
type: 'del_to_data',
value: index
})
}
render(){
return (
<div style={{padding: '40px'}}>
<Input
placeholder="请输入待办事项"
style={{ width: '350px',marginRight: '20px' }}
value={this.state.inputVal}
onChange={this.handleChange.bind(this)}
/>
<Button
type="primary"
onClick={this.handleClick.bind(this)}>
添加
</Button>
<List
bordered
style={{ marginTop: '30px',width: '435px',userSelect: 'none' }}
dataSource={this.state.data}
renderItem={(item,index) => (
<List.Item onDoubleClick={this.handleDoubleClick.bind(this,index)}>{item}</List.Item>
)}
/>
</div>
)
}
}
将reducer中的导出状态函数补充完整,修改state的具体逻辑在该函数中
三点需要注意:
- let newState = JSON.parse(JSON.stringify(state)) 可谓是该函数中最重要的一句代码,直接将reducer与state撇清关系;自此后续所有操作都围绕newState进行,可以说,初始化的defaultState的唯一作用只是为newState提供了数据的原始版本
- 该函数最后返回的是newState,而不是state,newState代表的永远是最新的数据
- action的作用只是通过属性type值区分逻辑操作而已,推荐使用switch...case语句
//store/reducer.js
//1.定义默认的状态数据
const defaultState = {
inputVal: '',
data: [
'尝遍人间甘辛味,言外冷暖我自知',
'一切安乐,无不来自困苦',
'如果脏了还用,就莫不如一开始就用带颜色的。白的就要纯白才行',
'君といると、月が绮丽です',
'得来不易的机会,会让所有的动物去做原来不喜欢做的事',
]
}
// 2.导出一个返回状态数据的函数
// 该函数有两个参数:state/action
export default (state = defaultState,action) => {
// reducer 只能接收和返回新的state,不能直接更改state,所以需要对state进行深拷贝
// 到这里其实 newState与state(最开始默认的defaultState)并无关系,
// 往后所有操作都是操作newState,显然,此时defaultState只是一个摆设
let newState = JSON.parse(JSON.stringify(state));
//reducer接收到一个事件,先判断不同的类型
switch (action.type) {
case 'change_inputVal':
newState.inputVal = action.value
break;
case 'add_to_data':
newState.data.push(newState.inputVal);
newState.inputVal = '';
break;
case 'del_to_data':
newState.data.splice(action.value,1);
break;
default:
break;
}
// 始终返回的都是深拷贝的newState
return newState;
}
4.store数据的订阅(subscribe:数据订阅)
subscribe(函数),该函数在store变化时,去更改react state中的数据
//TodoList.js
import React,{ Component } from 'react';
import { Button,Input,List } from 'antd';
// 引入store
import store from './store';
export default class TodoList extends Component{
constructor(){
super();
// getState获取store中的状态
this.state = store.getState();
// subscribe 订阅
// 订阅store ,一旦store 状态数据有更新,会进行触发
store.subscribe(this.storeChange.bind(this));
}
// store变化时,修改react state的函数
storeChange(){
this.setState(store.getState());
}
// 输入框变化
handleChange(e){
// this.setState({
// inputVal: e.target.value
// });
// 触发reducer(store)修改数据
const action = {
// type必传
type: 'change_inputVal',
value: e.target.value
};
//dispatch 事件派发
store.dispatch(action);
}
// 新增
handleClick(){
const action = {
type: 'add_to_data'
}
store.dispatch(action);
}
//双击删除
handleDoubleClick(index){
store.dispatch({
type: 'del_to_data',
value: index
})
}
render(){
return (
<div style={{padding: '40px'}}>
<Input
placeholder="请输入待办事项"
style={{ width: '350px',marginRight: '20px' }}
value={this.state.inputVal}
onChange={this.handleChange.bind(this)}
/>
<Button
type="primary"
onClick={this.handleClick.bind(this)}>
添加
</Button>
<List
bordered
style={{ marginTop: '30px',width: '435px',userSelect: 'none' }}
dataSource={this.state.data}
renderItem={(item,index) => (
<List.Item onDoubleClick={this.handleDoubleClick.bind(this,index)}>{item}</List.Item>
)}
/>
</div>
)
}
}
到这里redux版TodoList已经完成
6.actionType(封装action对象的type常量)
以上述redux版TodoList为例:
在没有封装actionCreaters的情况,actionType封装点有两处:
- reducer中switch...case中的action属性type
- 页面代码(TodoList)中的定义的action对象
新建action type常量文件
//store/actionTypes.js
//定义action type 常量值
export const CHANGE_INPUTVALUE = 'change_inputVal';
export const ADD_TO_DATA = 'add_to_data';
export const DEL_TO_DATA = 'del_to_data';
改造两处需封装的地方:
//store/reducer.js
import { CHANGE_INPUTVALUE,ADD_TO_DATA,DEL_TO_DATA } from './actionType';
//1.定义默认的状态数据
const defaultState = {
inputVal: '',
data: [
'尝遍人间甘辛味,言外冷暖我自知',
'一切安乐,无不来自困苦',
'如果脏了还用,就莫不如一开始就用带颜色的。白的就要纯白才行',
'君といると、月が绮丽です',
'得来不易的机会,会让所有的动物去做原来不喜欢做的事',
]
}
// 2.导出一个返回状态数据的函数
// 该函数有两个参数:state/action
export default (state = defaultState,action) => {
// reducer 只能接收和返回新的state,不能直接更改state,所以需要对state进行深拷贝
// 到这里其实 newState与state(最开始默认的defaultState)并无关系,
// 往后所有操作都是操作newState,显然,此时defaultState只是一个摆设
let newState = JSON.parse(JSON.stringify(state));
//reducer接收到一个事件,先判断不同的类型
switch (action.type) {
case CHANGE_INPUTVALUE:
newState.inputVal = action.value
break;
case ADD_TO_DATA:
newState.data.push(newState.inputVal);
newState.inputVal = '';
break;
case DEL_TO_DATA:
newState.data.splice(action.value,1);
break;
default:
break;
}
// 始终返回的都是深拷贝的newState
return newState;
}
//TodoList.js
import React,{ Component } from 'react';
import { Button,Input,List } from 'antd';
// 引入store
import store from './store';
//引入action type
import { CHANGE_INPUTVALUE,ADD_TO_DATA,DEL_TO_DATA } from './actionType';
export default class TodoList extends Component{
constructor(){
super();
// getState获取store中的状态
this.state = store.getState();
// subscribe 订阅
// 订阅store ,一旦store 状态数据有更新,会进行触发
store.subscribe(this.storeChange.bind(this));
}
// store变化时,修改react state的函数
storeChange(){
this.setState(store.getState());
}
// 输入框变化
handleChange(e){
const action = {
// type必传
type: CHANGE_INPUTVALUE,
value: e.target.value
};
//dispatch 事件派发
store.dispatch(action);
}
// 新增
handleClick(){
const action = {
type: ADD_TO_DATA
}
store.dispatch(action);
}
//双击删除
handleDoubleClick(index){
store.dispatch({
type: DEL_TO_DATA,
value: index
})
}
render(){
return (
<div style={{padding: '40px'}}>
<Input
placeholder="请输入待办事项"
style={{ width: '350px',marginRight: '20px' }}
value={this.state.inputVal}
onChange={this.handleChange.bind(this)}
/>
<Button
type="primary"
onClick={this.handleClick.bind(this)}>
添加
</Button>
<List
bordered
style={{ marginTop: '30px',width: '435px',userSelect: 'none' }}
dataSource={this.state.data}
renderItem={(item,index) => (
<List.Item onDoubleClick={this.handleDoubleClick.bind(this,index)}>{item}</List.Item>
)}
/>
</div>
)
}
}
7.actionCreaters(封装action对象)
在以上示例的基础上封装action对象,只有一处封装点:
- 将页面代码(TodoList)中定义的对象进行封装
- 由于action单独抽离,actionType也会随action的封装而去
//store/actionCreaters.js
//引入action type
import { CHANGE_INPUTVALUE,ADD_TO_DATA,DEL_TO_DATA } from './actionType';
// 定义action 对象
// 输入框变化action
export const CHANGE_INPUTVALUE_ACTION = (value) => {
return {
type: CHANGE_INPUTVALUE,
value
}
}
// 添加事项action
export const ADD_TO_DATA_ACTION = () => {
return {
type: ADD_TO_DATA
}
}
// 删除事项action
export const DEL_TO_DATA_ACTION = (value) => {
return {
type: DEL_TO_DATA,
value
}
}
//TodoList.js
import React,{ Component } from 'react';
import { Button,Input,List } from 'antd';
// 引入store
import store from './store';
//引入actionCreaters
import {
CHANGE_INPUTVALUE_ACTION,
ADD_TO_DATA_ACTION,
DEL_TO_DATA_ACTION
} from './store-react-redux/actionCreaters';
export default class TodoList extends Component{
constructor(){
super();
// getState获取store中的状态
this.state = store.getState();
// subscribe 订阅
// 订阅store ,一旦store 状态数据有更新,会进行触发
store.subscribe(this.storeChange.bind(this));
}
// store变化时,修改react state的函数
storeChange(){
this.setState(store.getState());
}
// 输入框变化
handleChange(e){
//dispatch 事件派发
store.dispatch(CHANGE_INPUTVALUE_ACTION(e.target.value));
}
// 新增
handleClick(){
const action = {
type: ADD_TO_DATA
}
store.dispatch(ADD_TO_DATA_ACTION());
}
//双击删除
handleDoubleClick(index){
store.dispatch(DEL_TO_DATA_ACTION(index));
}
render(){
return (
<div style={{padding: '40px'}}>
<Input
placeholder="请输入待办事项"
style={{ width: '350px',marginRight: '20px' }}
value={this.state.inputVal}
onChange={this.handleChange.bind(this)}
/>
<Button
type="primary"
onClick={this.handleClick.bind(this)}>
添加
</Button>
<List
bordered
style={{ marginTop: '30px',width: '435px',userSelect: 'none' }}
dataSource={this.state.data}
renderItem={(item,index) => (
<List.Item onDoubleClick={this.handleDoubleClick.bind(this,index)}>{item}</List.Item>
)}
/>
</div>
)
}
}
以上两中封装,说好听点呢,就是方便管理使用分类协作,说难听点呢,就是搞形式主义。。。
8.在redux的基础上整合react-redux
react-redux本身依赖于redux,安装前必须安装redux
npm install react-redux
整合react-redux,下述主要包括三部分:
- Provider提供器组件,用于在各个组件间传递store数据
- connect连接器函数用于映射state和action在props上(建立react与state/props的关系)
1.Provider提供器
Provider是一个提供器,只要使用了这个组件,组件里的其他所有组件都可以使用store了,这是react-redux的核心组件
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import Todolist from './TodoList'; //介绍state,派发,订阅
import Leijia from './Leijia';//介绍action类型常量,actionCreater
import TodoListRR from './TodoListRR';//将之前的Todolist改造成封装了action类型常量/actionCreater,并整合react-redux
// 加入 react-redux
import store from './store-react-redux';
// 引入提供器组件
import { Provider } from 'react-redux';
// 放在Provider中的标签组件都可以使用Provider提供的数据
ReactDOM.render(
// <Todolist />,
// <Leijia />,
// <TodoListRR />,
<Provider store={store}>
<TodoListRR />
</Provider>,
document.getElementById('root')
);
2.connect连接器
//TodoList.js
import React,{ Component } from 'react';
import { Button,Input,List } from 'antd';
// 引入store
//import store from './store';
//引入actionCreaters
import {
CHANGE_INPUTVALUE_ACTION,
ADD_TO_DATA_ACTION,
DEL_TO_DATA_ACTION
} from './store-react-redux/actionCreaters';
// 引入连接器函数,这时候会不需要store,将原先的store代码删除
import { connect } from 'react-redux';
class TodoListRR extends Component{
render(){
return (
<div style={{padding: '40px'}}>
<Input
placeholder="请输入待办事项"
style={{ width: '350px',marginRight: '20px' }}
value={this.props.inputVal}
onChange={this.props.handleChange.bind(this)}
/>
<Button
type="primary"
onClick={this.props.handleClick.bind(this)}>
添加
</Button>
<List
bordered
style={{ marginTop: '30px',width: '435px',userSelect: 'none' }}
dataSource={this.props.data}
renderItem={(item,index) => (
<List.Item onDoubleClick={this.props.handleDoubleClick.bind(this,index)}>{item}</List.Item>
)}
/>
</div>
)
}
}
//引入连接器后,不再是导出组件,而是导出connect(状态的映射,事件派发的映射)(组件名称)
export default connect()(TodoListRR);
3.state与action的映射
这两者的映射主要是建议与react 组件 props的关系,使得具体业务逻辑可以直接使用props上映射的state和action
import React,{ Component } from 'react';
import { Button,Input,List } from 'antd';
// 引入store
//import store from './store';
//引入actionCreaters
import {
CHANGE_INPUTVALUE_ACTION,
ADD_TO_DATA_ACTION,
DEL_TO_DATA_ACTION
} from './store-react-redux/actionCreaters';
// 引入连接器函数,这时候会不需要store,将原先的store代码删除
import { connect } from 'react-redux';
class TodoListRR extends Component{
render(){
return (
<div style={{padding: '40px'}}>
<Input
placeholder="请输入待办事项"
style={{ width: '350px',marginRight: '20px' }}
value={this.props.inputVal}
onChange={this.props.handleChange.bind(this)}
/>
<Button
type="primary"
onClick={this.props.handleClick.bind(this)}>
添加
</Button>
<List
bordered
style={{ marginTop: '30px',width: '435px',userSelect: 'none' }}
dataSource={this.props.data}
renderItem={(item,index) => (
<List.Item onDoubleClick={this.props.handleDoubleClick.bind(this,index)}>{item}</List.Item>
)}
/>
</div>
)
}
}
//状态映射
const mapStateToProps = (state) => {
return {
...state
}
}
// 事件派发映射
const mapDispatchToProps = (dispatch) => {
return {
// 新增
handleClick(){
dispatch(ADD_TO_DATA_ACTION());
},
//双击删除
handleDoubleClick(index){
dispatch(DEL_TO_DATA_ACTION(index));
},
handleChange(e){
dispatch(CHANGE_INPUTVALUE_ACTION(e.target.value));
}
}
}
//引入连接器后,不再是导出组件,而是导出connect(状态的映射,事件派发的映射)(组件名称)
export default connect(mapStateToProps,mapDispatchToProps)(TodoListRR);
tips:每一个组件内都这样写吗?过于麻烦
后续有时间在关注redux和react-redux在企业项目上的拆分