redux工作流
React UI库 框架
想要状态管理机制,使用Redux
如果想要异步交互,使用Redux-thunk或者Redux-saga中的一个
如果想要简化代码,获取业务逻辑与UI组件分离,那么使用react-redux
redux只能做同步操作,如果在redux中使用异步,需要借助中间件(redux-thunk,redux-saga)。
redux-saga学习成本较高,需要首先了解ES6的Generator函数
react-redux类组件->函数组件
用户写的组件-->函数组件-->无状态组件-->没有state
从效率来说,函数组件的运行效率比函数组件要高。
UI组件和容器组件
用户写的组件UI组件
使用react-redux的connect方法,将一个UI组件内部包裹一个容器组件,UI组件如果想要获取数据,props去获取。
直接将数据仓库绑定到更组件上,内部的子组件都可以使用。
1、安装redux
yarn add redux 或 yarn add react-redux
2、创建reducer
根目录下创建store文件夹,文件夹下创建reducer.js
//函数reducer
const initState = {
inputValue:'input的数据',
listData:[{
text:'备忘录1',
time:'2021-01-04',
status:'未完成'
}]
}
export default (state = initState,action)=>{
console.log('reducer');
//action就是组建中分发的那个action
if(action.type == 'TO_CHANGE_INPUT'){
return {
...state,
//给获取到的数据命名存入store
inputValue:action.value
}
}
if(action.type == 'TO_ADD'){
//修改仓库
let obj = {
text:state.inputValue,
time:'',
status:'未完成'
}
let {listData} = state;
listData.push(obj);
return {
...state,
listData
}
}
return{
...state
}
}
3、创建store
store下新建文件 index.js
//仓库 store
import {creatStore} from 'redux';
import reducer from './reducer';
//将reducer与store绑定,创建stroe并返回
export default creatStore(reducer);
4、使用
在constructor里 this.state = store.getState();将store中的数据导入到当前组件的state
订阅者模式
store中的数据更改,页面的订阅者发现,去修改页面的数据,让页面重新渲染
store.subscribe(this.storeChange)//订阅Redux的状态,如果redux中状态发生更改,需要监听并渲染
import store from '../store';
class ToDo extends Components{
constructor(props){
super(props);
this.state = store.getState();
store.subscribe(thihs.stateChange);//订阅者
}
stateChange = ()=>{
this.setState({
...store.getState()
});
}
inputChange=(e)=>{
//获取input数据,更改store中的数据
let action = {
type:'TO_CHANGE_INPUT',
value:e.target.value
};
//分发action
stroe.dispatch(action);
}
//添加
toAdd=(e)=>{
let action ={
type:'TO_ADD',
}
store.dispatch(action);
}
render(){
return (
<div style={{padding:20px}}>
<h2>备忘录</h2>
<div>
<Input tyoe="text" value={this.state.inputValue} onChange={this.inputCHange}/>
<Button type="primary" onClick={this.toAdd}>添加</Button>
</div>
<div>
{this.state.listData.map((item,index)=>{
<div key={index} style={{color:item.status === '未完成'?'red':'green'}}>
{item.text}$nbsp$nbsp{item.time}$nbsp$nbsp{item.status}
<span>{
item.status === '未完成' && <Button type="link" onClick={this.toChangeStatus.bind(this,index)}>完成</Button>
}</span>
</div>
})}
</div>
</div>
)
}
}
5、大型项目代码的拆分
创建项目,yarn add redux;
src之下新建文件夹pages,
pages之下新建test1.js和test2.js, //用来写组件
src之下新建文件夹store,
store中新建index.js //产生store
store之下新建文件夹actionCtreators,
actionCtreators之下新建文件test1Action.js和test2Action.js
store之下新建文件夹reducers,
reducers之下新建文件test1Reducer.js和test2Reducer.js,index.js
store之下新建文件actionType.js
//test1组件
//展示store的数据
import React,{Component} from 'react';
import {changeTest1A} from '../store/actionCreators/test1Action'
import {changeTest2A} from '../store/actionCreators/test2Action'
class Test1 extends Component{
constructor(props){
super(props);
this.state = store.getState();
store.subscribe(this.changeState);//订阅
//若是此处只想要test1中的数据this.state = store.getState().tets1;
}
changeTest1 = ()=>{
//分发action,action对象
store.dispatch(changeTest1A('123'));
}
changeTest2 = ()=>{
//更该test的基本数据
store.dispatch(changeTest2('2323'));
}
changeState = ()=>{
this.setState(store.getState())
}
render(){
return(
<div>
{JSON.stringify(this.state)}
<button onClick={this.changeTest1}>更改test1的数据</button>
<button onClick={this.changeTest2}>更改test2的数据</button>
</div>
)
}
}
export default Test1;
//Test1Action创造器
import {CHANGE_TEST1} from '../actionTypes'
export const chanTest1A = (value)=>{
//返回的是action对象
retun {
type:'CHANGE_TEST1',//必不可少,代表action的类型
value
}
}
//Test1reducer,业务逻辑处理
import {CHANGE_TEST1} from '../actiontypes';
let test1State = {
msg:'test1'
};
export default (state = test1State,action)=>{
//业务逻辑处理
if(action.type === CHANGE_TEST1){
return {
...state,
msg:action.value
}
}
return state;
}
// Test2组件
// 使用React-Redux去做
// yarn add react-redux
import React from 'react';
import { connect } from 'react-redux';
import { changeTest2A, toGetDataA } from '../store/actionCreators/test2Action';
const Test2 = (props) => {
return (
<div>
<h3>react-redux</h3>
{JSON.stringify(props)}
<button onClick={props.changeTest2}>修改test2的数据</button>
<br />
<button onClick={props.toGetData}>获取数据</button>
{JSON.stringify(props.arr)}
</div>
);
}
// connect(mapStateToProps)(Test2)
// connect的第一个参数mapStateToProps是一个函数,函数有一个参数state,代表store中的所有的数据。返回的对象,就是UI组件的props的数据
const mapStateToProps = (state, ownProps) => {
return {
test: state.test2.msg,
value: 'hello',
arr: state.test2.arr
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
changeTest2: () => {
// 分发action
dispatch(changeTest2A('0000'))
},
toGetData: () => {
// 分发action
dispatch(toGetDataA())
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Test2);
// export default connect(state => state.test2)(Test2); 如果store中只需要test2的数据
// export default connect(state => state)(Test2);
// Test2Action创建器
import { CHANGE_TEST2, TO_GET_DATA } from '../actionTypes'
export const changeTest2A = (value) => {
return {
type: CHANGE_TEST2,
value
};
}
export const toGetDataA = () => {
return {
type: TO_GET_DATA
};
}
// Test2reducer,业务逻辑处理
import { CHANGE_TEST2, TO_GET_DATA } from '../actionTypes'
import axios from 'axios';
let test2State = {
msg: 'test2',
arr: []
};
export default (state = test2State, action) => {
if (action.type === CHANGE_TEST2) {
return {
...state,
msg: action.value
};
}
// reducer是一个纯函数,它的返回值不能有除参数以外的决定,如果写了异步操作,需要用中间件
if (action.type === TO_GET_DATA) {
// 获取后台数据,更改store数据,然后再返回
axios.get('http://134.175.154.93:8099/manager/category/findAllCategory').then((res) => {
let arr = res.data.data;
return {
...state,
arr
}
}).catch((error) => {
console.log(error);
})
}
return state;
}
//action的type的类型
export const CHANGE_TEST1 = 'CHANGE_TEST1';
export const CHANGE_TEST2 = 'CHANGE_TEST2';
export const TO_GET_DATA = 'TO_GET_DATA';
//reducers文件夹下的index.js,整合所有的reducer,导出一个合并之后reducer
import {combineReducers} from 'redux';
import test1Reducer from './test1Reducer';
import test2Reducer from './test2Reducer';
const reducer = combineReducers({
//需要整合的reducer
// 大仓库中有两个初始化数据,写的什么属性,初始化数据放到什么属性上
test1:test1Reducer,
test2:test2Reducer
})
export default reducer;
//store文件夹下的index.js
import {createStore} from 'redux';
import reducer from './reducers'
// 关于谷歌插件(REDUX_DEVTOOLS)的配置
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;
react-redux
将所有组件分成两大类:
UI组件(负责UI的呈现)和容器组件(负责管理数据和逻辑)
UI组件
1、只负责UI的呈现,不带有任何业务逻辑
2、没有状态,即不适用this.state这个变量
3、所有数据都有参数(this.props)提供
4、不适用任何Redux的API
5、因为不含有状态,UI组件又称为“纯组件”,即他跟纯函数一样,纯粹由参数决定它的值。
容器组件
1、负责管理数据和业务逻辑,不负责UI的呈现。
2、带有内部状态
3、使用redux的API
4、容器组件包裹UI组件
UI组件和容器组件的结合
1、如果一个组件既有UI又有业务逻辑,那么将他拆分成两层结构:外面是一个容器组件,里面包了一个UI组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。
2、React-Redux规定,所有的UI组件都由用户提供,容器组件则是由React-Redux自动生成。
React-Redux中的connect()
用于从UI组件生成容器组件
React-Redux中的mapStateToProps()
1、mapStateToProps是一个函数。他的作用是建立一个从外部state对象到UI组件的props对象的映射关系。执行后返回一个对象,里面的每一个键值对就是一个映射(UI组件可以访问的数据)。
2、mapStateToProps会订阅(绑定)store,每当state更新的时候,就会从自动执行,重新计算UI组件的参数,从而出发UI组件的重新渲染。
3、mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发UI组件重新渲染。
4、如果connect方法省略mapStateToProps参数,那么UI组件就不会订阅Store,就是说Store的更新不会引起UI组件的更新。
connect(mapStateToProps,mapDispatchToProps)(UICom)
React-Redux中的mapDispatchToProps()
1、mapDispatchToProps是connect函数的第二个参数,用来建立UI组件的参数到store.dispatch方法的映射。也就是说,他定义了用户的那些操作应该当做Action,传给Store。他可以是一个函数,也可以使一个对象。
2、如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。
3.如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。
React-Redux的<Provider>
组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。一种解决方法是将state对象作为参数,传入容器组件。但是容器组件可能在很深的层级,一级级将state传下去就很麻烦。React-Redux 提供Provider组件,可以让容器组件拿到state。在根组件外面包了一层Provider,App的所有子组件都可以拿到state了。它的原理是React组件的context属性,store放在了上下文对象context上面。React-Redux自动生成的容器组件的代码,就类似下面这样,然后子组件就可以从context拿到store。
安装
yarn add react-redux
使用
1.在项目index.js里,引入Provider,引入store,然后使用
import { Provider } from 'react-redux'
import store from './store'
<Provider store={store}><App /></Provider>
<Provider>
是一个提供器,只要使用了这个组件,组件里边的其它所有组件都可以使用store了
2.使用store中的数据
在需要使用store数据的地方,引入连接器
import {connect} from ‘react-redux’ //引入连接器
创建映射关系,把原来的state映射成组件中的props属性
const stateToProps = (state)=>{
return {
inputValue : state.inputValue
}
}
导出组件
export default connect(mapStateToProps,mapDispatchToProps)(TodoList)
mapStateToProps传入所有state,返回指定的state数据
function mapStateToProps(state) {
return { todos: state.todos }
}
mapDispatchToProps,传入dispatch,返回的对象中的方法会在组件的props中
const mapDispatchToProps = (dispatch) => {
return {
inputChange(e) {
dispatch(changeInputAction(e.target.value));
}
}
}
connect的作用是把UI组件(无状态组件)和业务逻辑代码的分开,然后通过connect再连接到一起,让代码更清晰和易于维护。这也是React-Redux最大的优点。