Redux 状态管理(重点)
Redux 对于 JavaScript 应用而言是一个可预测状态的容器。换言之,它是一个应用数据流框架,而不是传统的像 underscore.js 或者 AngularJs 那样的库或者框架。
Redux 最主要是用作应用状态的管理。简言之,Redux 用一个单独的常量状态树(对象)保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,一个新的对象就会被创建(使用 actions 和 reducers)。
Redux 由下面四个部分组成:
- Reducer
- Store
- ActionCreator
- Component View
下面的代码都在同一个页面中编写,如果在不同的页面编写,需要额外添加导入导出等数据传递接收方法。
QQ图片20191223193413.png
下载模块
npm install redux -S
# or
yarn add redux
引入模块
在 redux 中取出 createStore 对象,这个对象的作用是创建仓库的,仓库中存储着要用到的项目中的属性和方法。
import { createStore } from "redux"
创建 reducers
reducers 翻译过来是减震器、还原器的意思。
组件视图层触发的操作,执行的就是这个 reducers,通过业务逻辑的处理,最终将处理后的数据保存到 store 中。
// 初始的状态
const stateDefault = {
}
// 创建 reducers,把初始的状态带入进入,让两者关联在一起。
const reducers = function( state=stateDefault, action ){
// 在此处应根据action.type来决定对state进行增删改查哪种操作
// 暂时不做任何操作,直接把前一次的state直接返回给store
return state
}
创建 store
保存状态的容器,不仅仅保存一些数据,reducers中还存放在修改数据的业务逻辑代码。
const store = createStore(reducers)
派发动作 actionCreator
组件视图层的用户操作,dispatch触发的就是reducers方法。
reducers内容中描述着修改store中state的业务逻辑。
var action = {
type : 'ADD',
val : 'abc123'
}
store.dispatch( action )
store 中的 state 发生变化后,视图层不会自动更新,可以通过 subscribe 把 App.js 中的 setState 的代码注册进来,让 App 组件视图层更新。
获取 state
组件视图层中获取 redux 中的 state 数据
store.getState()
订阅 subscribe
每当 reducers 执行完毕后,状态修改完毕后,自动触发的事件。
const unsubscribe = store.subscribe(function(){
})
取消订阅
// 一定要记得在销毁之前清除监听不然会报错
componentWillUnmount() {
this.subscribe()
}
注入到组件
同样是单项数据流原则,只能一层一层传入数据。所以后期应该用 react-redux 模块。
<App store={store}></App>
在组件中使用
this.props.store.dispatch()
// or
this.props.store.getState()
React-Redux(重点)
React-Redux 组件的作用是对 react 与 redux 进行连接,如果在 react 项目中直接使用 redux,那么需要把 redux 中的 store 数据,通过 props 属性,一层一层传递到组件中,这样做太麻烦了,所以可以借助 React-Redux 模块,可以跨层级的在任意组件中直接把 Redux 中的 store 数据取出来。
下载
npm install react-redux -S
# or
yarn add react-redux
引入
react-redux 模块唯一的作用就是用来连接 react 和 redux,它里面只有2个子对象
import { Provider, connect } from 'react-redux'
- Provider: 提供。这是一个组件,这个组件中传入 store,即这个组件提供了redux中的store。
- connect: 连接。无论是子组件还是其他后代组件,只要使用connect函数进行连接,就可以关联到redux中的store。
Provider组件
Provider组件,通常是最顶层,使用 Provider 组件把 store 共享出来。
react-redux 仅仅解决了组件间传递数据这个问题,所以 redux 本身还需要以前的方式编写出来,即在 redux 中把 store 创建出来,然后以属性的形式,挂载到 Provider 组件上。
<Provider store={store}>
<App />
</Provider>
Provider组件把redux的store共享出来之后,别的组件如果想使用Provider组件中的store数据,需要用 connect 将 Provider 及容器组件和UI组件连接到一起。
因为使用 react-redux 就是为了在不同层级都可以直接使用 redux 的 store,所以容器组件和UI组件随便写在哪里都可以。
容器组件
容器组件也被称为聪明的组件,容器组件和UI组件要组合在一起,容器组件跨层级的直接去Provider组件中获取数据,然后容器组件把得到的数据挂载到UI组件的属性上。
容器组件相当于直接把 redux 中的 State 和 Dispatch 映射到它对应的UI组件的属性上,然后UI组件通过 props 就可以拿到 State 和 Dispatch 了。
在 View 层使用组件时,使用该容器组件,这样容器组件里的UI组件就可以直接通过 props 拿到 State 和 Dispatch 了。
// 这是容器组件,也被称为聪明的组件
const mapStateToProps = function(state, props){
return {}
}
const mapDispatchToProps = function(dispatch){
return {}
}
export default connect( mapStateToProps, mapDispatchToProps )( UI组件 )
UI组件
UI组件也被称为展示组件,也被称为木偶组件,UI组件的父层组件是容器组件,容器组件将 redux 中的数据,挂载到了UI组件的属性上。
render(){
return (
<div>就是普通的组件,这里可以用this.props接收父层容器组件中映射过来的状态和方法</div>
)
}
容器组件会把dispatch映射到展示组件中,用户调用dispatch时,就会执行reducers中函数,进而改变了redux的state。
在reducers中,修改state时,一定要改变栈值,这样与之关联的视图层都会重新渲染。
最简单的修改栈值方法:
JSON.parse(JSON.stringify(oldState))
combineReducers 合并
combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数合并成一个最终的 reducer 函数。
常见使用场景:项目模块特别多,每个模块又需要用到很多数据,这些数据如果都写在一起,会显得代码结构特别乱,所以要把每一个模块(功能)单独用一个reducers描述,最终使用 combineReducers 对所有的 reducers 合并,类似 modules 的概念。
import { createStore, combineReducers } from 'redux';
const store = createStore(combineReducers({a:reducers1, b:reducers2}));
调用的时候,还是通过 store.dispatch 来调用,如果多个reducers中有相同命名的 type,那么相关代码都会执行。
获取 state 时,需要写 reducers 的 key 名字。
applyMiddleware 中间件
中间件函数,Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。
项目中必然会涉及到异步代码,这种异步代码会写在哪里?
-
方案1:直接写在 UI 组件中,当异步代码执行完后,调用 dispatch 方法,这样做的好处是 dispatch 是同步的,特别干净。
-
方案2:写到容器组件中,在 dispatch 中描述异步代码,这样做的好处是一个功能的多种状态会被包装在一起。
redux-thunk
不要把 redux-thunk 想象的多么的高大上,它唯一的作用就是让 dispatch 中可以写函数。
写函数的目的是因为函数中可以包含很多业务逻辑,也可以把多个 dispatch 包裹在一起。
比如用户一个添加动作,可以分别开始添加、添加中、添加完成、添加失败等等各种状态,这些状态合在一起描述完整的添加过程。
store
yarn add redux-thunk
#or
npm i redux-thunk -S
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const stateDefault = {
text : "",
todos : []
}
const reducers = function(state=stateDefault, action){
switch( action.type ){
case 'ADD':
console.log('ADD')
return {
todos : [...state.todos],
text : '正在添加数据..'
}
case 'ADD-SUCCESS':
console.log('ADD-SUCCESS:', action.data)
return {
todos : [...state.todos, action.data],
text : '成功'
};
case 'GET':
console.log('GET:', action.data)
return {
todos: [...action.data]
}
default:
return state;
}
}
const store = createStore(reducers, applyMiddleware(thunk));
export default store;
db.json
db.json 模拟后端接口,向这里添加新数据。
{
"todos":[
{"id":"1", "title":"苹果"},
{"id":"2", "title":"橘子"},
{"id":"3", "title":"香蕉"},
{"id":"4", "title":"西瓜"},
{"id":"5", "title":"菠萝"}
]
}
千万不要把 db.json 文件放在当前目录中,如果使用 post 之类的方法修改了该文件,相当于当前项目中有文件做了更改,浏览器是会自动刷新的。
json-server db.json --port 3001
容器组件
import React, { Component } from 'react';
import './App.css';
import { connect } from 'react-redux';
import axios from 'axios';
const mapStateToProps = state=>({
todos : state.todos,
text : state.text
})
const mapDispatchToProps = dispatch=>({
add(){
dispatch((dispatch, getState)=>{
dispatch({type:'ADD'})
axios.get('/db.json').then(res=>{
dispatch({type:'ADD-SUCCESS', payload:res.data})
console.log( getState().todos )
}).catch(err=>{
dispatch({type:'ADD-FAIL'})
})
});
},
get(){
dispatch(function(dispatch, getState){
axios.get("http://127.0.0.1:3001/todos").then(res=>{
console.log('data:', res.data);
dispatch({type:'GET', data:res.data})
})
})
}
})
class App extends Component {
componentDidMount(){
this.props.get();
}
render() {
return (
<div>
<button onClick={()=>{this.props.add()}}>ADD</button>{ this.props.text }
{this.props.todos.map((item,ind)=><li key={ind}>{item.title}</li>)}
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux-promise-middleware
与thunk一样,解决的都是异步问题。
实际就是允许payload为promise对象,然后对type进行补充,例如下文,看上去执行的是ADD,实际上并没有执行ADD,过程中执行ADD_PENDING,成功时执行ADD_FULFILLED,失败时执行ADD_REJECTED。
store
yarn add redux-promise-middleware
#or
npm install redux-promise-middleware -S
import { createStore, applyMiddleware } from 'redux';
import reduxpromisemiddleware from 'redux-promise-middleware'
const stateDefault = {
todos : [],
text : ''
}
const reducers = function(state=stateDefault, action){
//console.log('action:', action)
switch( action.type ){
case 'ADD':
//console.log('ADD');
return {
todos : [...state.todos],
text : '开始..'
};
case 'ADD_PENDING':
//console.log('ADD_PENDING');
return {
todos : [...state.todos],
text : '进行中'
};
case 'ADD_FULFILLED':
//console.log('ADD_FULFILLED');
return {
todos : [...state.todos, {...action.payload}],
text : '已完成'
};
case 'ADD_REJECTED':
//console.log('ADD_FULFILLED');
return {
todos : [...state.todos, {...action.payload}],
text : '失败'
};
case 'GET_FULFILLED':
return {
todos : [...action.payload],
text : '初始化'
};
default:
return state;
}
}
const store = createStore(reducers, applyMiddleware(reduxpromisemiddleware));
export default store;
如果想使用多个 Middleware 可以用逗号分割,例如 applyMiddleware(thunk, reduxpromisemiddleware)
组件
import React from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
const mapStateToProps = function(state, props){
return {
text : state.text,
todos : state.todos
}
}
const mapDispatchToProps = (dispatch)=>({
add(val){
// 这个会执行ADD
dispatch({type:'ADD'})
setTimeout(()=>{
// 这个不会执行ADD,直接执行ADD_PENDING
dispatch({type:'ADD', payload:new Promise(function(resolve){
setTimeout(()=>{
axios.post('http://localhost:3002/todos', {val}).then(result=>{
// 这个执行ADD_FULFILLED
resolve(result.data)
})
}, 500)
})})
}, 500)
},
get(){
dispatch({type:'GET', payload:new Promise(function(resolve){
setTimeout(()=>{
axios.get('http://localhost:3002/todos').then(result=>{
resolve(result.data)
})
}, 500)
})})
}
})
class App extends React.Component {
componentDidMount(){
this.props.get();
}
render(){
return (
<div>
<input ref="input1" />{this.props.text}
<button onClick={()=>{this.props.add(this.refs.input1.value)}}>
添加
</button>
{this.props.todos.map(item=>(
<li key={item.id}>{item.val}</li>
))}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux-saga
把所有的过程写在generator函数中,这样语义就会很舒服。
store
import { createStore, applyMiddleware } from 'redux';
import axios from 'axios';
import createSagaMiddleware from 'redux-saga';
import { call, put, select } from 'redux-saga/effects';
// store仓库使用saga中间件
const sagaMiddleware = createSagaMiddleware();
// 添加数据
const postData = (payload)=>new Promise((resolve, reject)=>{
axios.post('http://localhost:3002/todos', payload).then(res=>{
resolve(res.data);
}).catch(err=>{
reject(err);
})
});
// 获取数据
const getData = ()=>new Promise((resolve, reject)=>{
axios.get('http://localhost:3002/todos').then(res=>{
resolve(res.data);
}).catch(err=>{
reject(err);
})
});
// generator函数 (添加的一系列动作)
const addSaga = function * (payload){
try{
const data = yield call(()=>postData(payload)); //添加数据,获取响应
yield put({ type: 'ADD-SUCCESS', payload: data}); // 执行别的action
//const todos = yield select(state =>state.todos); // 获取todos
}catch(err){
yield put({ type: 'ADD-FAIL', payload: err}); // 执行别的action
}
}
// generator函数 (获取的一系列动作)
const getSaga = function * (){
const data = yield call(()=>getData());
yield put({ type: 'GET-SUCCESS', payload: data});
}
// 默认状态
const stateDefault = {
todos : [],
text : ''
}
// reducers
const reducers = function(state=stateDefault, action){
//console.log('action:', action)
switch( action.type ){
case 'ADD':
console.log('ADD')
sagaMiddleware.run(()=>addSaga(action.payload));
return {
...state,
text : '添加..'
};
case 'ADD-SUCCESS':
console.log('ADD-SUCCESS', action.payload)
return {
text : '添加成功',
todos : [...state.todos, action.payload]
}
case 'ADD-FAIL':
console.log('ADD-FAIL')
return {
...state,
text : '添加失败'
}
case 'GET':
console.log('GET')
sagaMiddleware.run(()=>getSaga());
return {
...state,
text : '获取..'
};
case 'GET-SUCCESS':
console.log('GET-SUCCESS', action.payload)
return {
text : '获取成功',
todos : [...action.payload]
}
default:
return state;
}
}
const store = createStore(reducers, applyMiddleware(sagaMiddleware));
export default store;
组件
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = function(state, props){
return {
text : state.text,
todos : state.todos
}
}
const mapDispatchToProps = function(dispatch){
return {
add(val){
dispatch({type:'ADD', payload:{val}});
},
get(){
dispatch({type:'GET'});
}
}
}
class App extends React.Component {
componentDidMount(){
this.props.get();
}
render(){
return (
<div>
<input ref="input1" />{this.props.text}
<button onClick={()=>{this.props.add(this.refs.input1.value)}}>
添加
</button>
{this.props.todos.map(item=>(
<li key={item.id}>{item.val}</li>
))}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
网友评论