十八、Redux
18.1 理解Flux架构
在2013年,Facebook让React亮相的同时推出了Flux框架,React的初衷实际上是用来替代jQuery的,Flux实际上就可以用来替代Backbone.js,Ember.js等一系列MVC架构的前端JS框架。
其实Flux在React里的应用就类似于Vue中的Vuex的作用,但是在Vue中,Vue是完整的mvvm框架,而Vuex只是一个全局的插件。
React只是一个MVC中的V(视图层),只管页面中的渲染,一旦有数据管理的时候,React本身的能力就不足以支撑复杂组件结构的项目,在传统的MVC中,就需要用到Model和Controller。Facebook对于当时世面上的MVC框架并不满意,于是就有了Flux, 但Flux并不是一个MVC框架,他是一种新的思想。
- View: 视图层
- ActionCreator(动作创造者):视图层发出的消息(比如mouseClick)
- Dispatcher(派发器):用来接收Actions、执行回调函数
- Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面
Flux的流程:
- 组件获取到store中保存的数据挂载在自己的状态上
- 用户产生了操作,调用actions的方法
- actions接收到了用户的操作,进行一系列的逻辑代码、异步操作
- 然后actions会创建出对应的action,action带有标识性的属性
- actions调用dispatcher的dispatch方法将action传递给dispatcher
- dispatcher接收到action并根据标识信息判断之后,调用store的更改数据的方法
- store的方法被调用后,更改状态,并触发自己的某一个事件
- store更改状态后事件被触发,该事件的处理程序会通知view去获取最新的数据
18.2 理解Redux单向数据流流程
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
- 代码结构
- 组件之间的通信
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
需要使用Redux的项目:
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
从组件层面考虑,什么样子的需要Redux:
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
Redux的设计思想:
- Web 应用是一个状态机,视图与状态是一一对应的。
- 所有的状态,保存在一个对象里面(唯一数据源)。
注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。
18.3 redux使用三大原则 -必须掌握
- Single Source of Truth(唯一的数据源)
- State is read-only(状态是只读的) - 无法直接修改状态
- Changes are made with pure function(数据的改变必须通过纯函数完成)
$ cnpm i redux -S
src/13_redux/store/index.js 创建状态管理器
// src/13_redux/store/index.js
// 1.引入 redux 创建 状态管理器的方法
import { createStore } from 'redux' // createStore 方法不被推荐使用 推荐使用 reduxtoolkit
// 2.创建纯函数 reducer --- 数据的改变必须通过纯函数完成
// 纯函数的参数为 state 以及 action
// state的参数可以设置默认值,默认值即为所有状态的初始化的数据
// action 即为 描述 如何修改状态的对象,有两个参数,固定属性type 以及 约定的属性 payload
// 函数内部依据action.type属性 返回不同的状态
const reducer = (state = {
bannerList: [],
proList: [],
kindList: []
}, action) => {
switch (action.type) {
case 'CHANGE_BANNER_LIST':
return {...state, bannerList: action.payload } // 状态是只读的 - 无法直接修改状态
case 'CHANGE_PRO_LIST':
return {...state, proList: action.payload }
case 'CHANGE_KIND_LIST':
return {...state, kindList: action.payload }
default:
return state
}
}
// 3.创建状态管理器 --- 唯一的数据源
const store = createStore(reducer)
console.log(store)
/**
*@@observable: observable()
dispatch: dispatch(action) // 组件触发修改状态的函数
getState: getState() // 组件获取状态的函数
replaceReducer: replaceReducer(nextReducer) // 替换修改状态的reducer
subscribe: subscribe(listener) // 订阅状态管理器的变化
*/
// 4.暴露状态管理器
export default store
src/index.js 配置状态管理器 - 状态管理器修改订阅视图的更新
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 5.引入状态管理器
import store from './13_redux/store'
import App from './13_redux/App'
const root = ReactDOM.createRoot(document.getElementById('root'));
function render () {
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
}
render()
// 6.状态管理器发生改变,订阅
store.subscribe(render) // store的状态发生改变,就会重新执行render函数
src/13_redux/App.jsx使用状态管理器
// src/13_redux/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
const App = () => {
return (
<div>
<Home />
<hr />
<Kind />
</div>
);
};
export default App;
src/13_redux/views/Kind.jsx
// src/13_redux/views/Kind.jsx
import React, { useEffect } from 'react';
// 7.组件引入store,通过store.getState()方法获取状态管理器状态,通过store.dispatch 修改状态
import store from '../store'
// kindList
const Kind = () => {
// const state = store.getState()
// console.log(state)// { bannerList:[], proList: [], kindList: [] }
// const kindList = store.getState().kindList // []
const { kindList } = store.getState() // []
useEffect(() => {
fetch('http://121.89.205.189:3000/api/pro/categorylist')
.then(res => res.json())
.then(res => {
store.dispatch({
type: 'CHANGE_KIND_LIST',
payload: res.data
})
})
}, [])
return (
<div>
分类
<ul>
{
kindList && kindList.map((item, index) => {
return <li key = { index }>{ item }</li>
})
}
</ul>
</div>
);
};
export default Kind;
src/13_redux/views/Home.jsx
// src/13_redux/views/Home.jsx
import React from 'react';
import { useMount } from 'ahooks'; // 确保安装过 ahooks cnpm i ahooks -S
// 7.组件引入store,通过store.getState()方法获取状态管理器状态,通过store.dispatch 修改状态
import store from '../store'
// bannerList proList
const Home = () => {
const { bannerList, proList } = store.getState()
useMount(() => {
fetch('http://121.89.205.189:3000/api/banner/list').then(res => res.json()).then(res => {
// 修改状态
store.dispatch({
type: 'CHANGE_BANNER_LIST',
payload: res.data
})
})
fetch('http://121.89.205.189:3000/api/pro/list').then(res => res.json()).then(res => {
// 修改状态
store.dispatch({
type: 'CHANGE_PRO_LIST',
payload: res.data
})
})
})
return (
<div>
首页
{
bannerList && bannerList.map(item => {
return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
})
}
<ul>
{
proList && proList.map(item => {
return (
<li key = { item.proid }>{ item.proname }</li>
)
})
}
</ul>
</div>
);
};
export default Home;
以上代码是最最最最最最最基本的redux的使用,现在企业里基本上不使用这种原始的方法
18.4 redux + react-redux配合使用 -必须掌握
redux是属于js的状态管理模式,不独属于react,在react中需要结合 react-redux 模块使用
react-redux提供两个核心的api:
-
Provider: 提供store
<Provider store = { store }><App /></store> -
connect: 用于连接容器组件和展示组件
-
Provider
根据单一store原则 ,一般只会出现在整个应用程序的最顶层。
-
connect
语法格式为
connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)一般来说只会用到前面两个,它的作用是:
- 把
store.getState()的状态转化为展示组件的props - 把
actionCreators转化为展示组件props上的方法
- 把
-
只要上层中有Provider组件并且提供了store, 那么,子孙级别的任何组件,要想使用store里的状态,都可以通过connect方法进行连接。如果只是想连接actionCreators,可以第一个参数传递为null
$ cnpm i react-redux -S
18.4.1 创建状态管理器
src/14_redux_react-redux/store/index.js
// src/14_redux_react-redux/store/index.js
import { createStore } from 'redux'
const reducer = (state = {
bannerList: [],
proList: [],
kindList: []
}, { type, payload }) => {
switch (type) {
case 'CHANGE_BANNER_LIST':
return {...state, bannerList: payload }
case 'CHANGE_PRO_LIST':
return {...state, proList: payload }
case 'CHANGE_KIND_LIST':
return {...state, kindList: payload }
default:
return state
}
}
const store = createStore(reducer)
export default store
1.引入 createStore 方法(新版本中建议使用rtk)
2.创建reducer(包含初始化状态,包含了修改状态的标识,返回了新的状态(对象合并))
3.创建store
4.暴露store
18.4.2 使用react-redux
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 5.引入状态管理器 以及 辅助的组件Provider
import { Provider } from 'react-redux'
import store from './14_redux_react-redux/store'
import App from './14_redux_react-redux/App'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
{/* 6.组件形式调用状态管理器 */}
<Provider store={ store }>
<App />
</Provider>
</React.StrictMode>
</ErrorBoundary>
);
使用了 react-redux 提供的组件组件 Provider 配合 store 完成传递数据
src/14_redux_react-redux/App.jsx
// src/14_redux_react-redux/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
const App = () => {
return (
<div>
<Home />
<hr/>
<Kind />
</div>
);
};
export default App;
src/14_redux_react-redux/views/Home.jsx
// src/14_redux_react-redux/views/Home.jsx
import React from 'react';
import { useMount } from 'ahooks'
import { connect } from 'react-redux'
const Home = ({ bannerList, proList, getBannerListData, getProListData }) => {
useMount(() => {
getBannerListData()
getProListData()
})
return (
<div>
首页
{
bannerList && bannerList.map(item => {
return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
})
}
<ul>
{
proList && proList.map(item => {
return (
<li key = { item.proid }>{ item.proname }</li>
)
})
}
</ul>
</div>
);
};
// export default connect(state => {
// return {
// bannerList: state.bannerList,
// proList: state.proList
// }
// }, dispatch => {
// return {
// getBannerListData () {
// fetch('http://121.89.205.189:3000/api/banner/list')
// .then(res => res.json())
// .then(res => {
// dispatch({
// type: 'CHANGE_BANNER_LIST',
// payload: res.data
// })
// })
// },
// getProListData () {
// fetch('http://121.89.205.189:3000/api/pro/list')
// .then(res => res.json())
// .then(res => {
// dispatch({
// type: 'CHANGE_PRO_LIST',
// payload: res.data
// })
// })
// }
// }
// })(Home);
// (state) => { return { proList: state.proList }}
// ({ proList }) => { return { proList: proList }}
// ({ proList }) => { return { proList }}
// ({ proList }) => ({ proList })
export default connect(({ bannerList, proList }) => ({ bannerList, proList }), dispatch => ({
getBannerListData () {
fetch('http://121.89.205.189:3000/api/banner/list')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_BANNER_LIST',
payload: res.data
})
})
},
getProListData () {
fetch('http://121.89.205.189:3000/api/pro/list')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_PRO_LIST',
payload: res.data
})
})
}
}))(Home);
src/14_redux_react-redux/views/Kind.jsx 业务逻辑写到容器组件中
// src/14_redux_react-redux/views/Kind.jsx
import React, { useEffect } from 'react';
// 7.通过 react-redux 模块提供的 connect 高阶组件 将这个文件分为 容器组件和展示组件
// Kind 展示数据
// connect(mapStateToProps, mapDispatchToProps)(Kind) 容器组件
// mapStateToProps 函数 有默认参数 state ===》 store.getState() 数据通过props传递给展示组件
// mapDispatchToProps 函数 有默认参数 dispatch ==> 可以通过 dispatch 触发 reducer 修改状态,可以在此函数中返回 给展示组件 执行的方法
import { connect } from 'react-redux'
const Kind = (props) => {
const { kindList, getKindListData } = props
useEffect(() => {
getKindListData()
}, [])
return (
<div>
分类
<ul>
{
kindList && kindList.map((item, index) => {
return <li key = { index }>{ item }</li>
})
}
</ul>
</div>
);
};
const mapStateToProps = (state) => { // 全局管理的所有的状态
return { // ==> props.kindList
kindList: state.kindList
}
}
const mapDispatchToProps = (dispatch) => {
return {
getKindListData () { // props.getKindListData()
fetch('http://121.89.205.189:3000/api/pro/categorylist')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_KIND_LIST',
payload: res.data
})
})
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Kind);
vuex中 可以把异步操作提取到vuex中的actions中,react中异步操作方式会有很多,最常见的就是 redux-thunk
18.5 redux+react-redux分模块使用 - 了解
如果项目很大,或者项目划分比较明确,需要将状态管理器也分模块去处理
假设现在有一个home模块,管理 bannerList 以及 proList
还有一个模块 kind 模块,管理 kindList
src/15_redux_react-redux_combine/store/modules/home.js
// src/15_redux_react-redux_combine/store/modules/home.js
const reducer = (state = {
bannerList: [],
proList: []
}, { type, payload }) => {
switch (type) {
case 'CHANGE_BANNER_LIST':
return { ...state, bannerList: payload }
case 'CHANGE_PRO_LIST':
return { ...state, proList: payload }
default:
return state
}
}
export default reducer
src/15_redux_react-redux_combine/store/modules/kind.js
// src/15_redux_react-redux_combine/store/modules/kind.js
const reducer = (state = {
kindList: []
}, { type, payload }) => {
switch (type) {
case 'CHANGE_KIND_LIST':
return { ...state, kindList: payload }
default:
return state
}
}
export default reducer
整合多个reducer 为一个,因为 有一个原则 : 单一数据源 的原则
src/15_redux_react-redux_combine/store/index.js
// src/15_redux_react-redux_combine/store/index.js
import { createStore, combineReducers } from "redux";
import homeReducer from './modules/home'
import kindReducer from './modules/kind'
const reducer = combineReducers({
home: homeReducer, // home 模块 使用了 homeReducer
kind: kindReducer // kind 模块 使用了 kindReducer
})
const store = createStore(reducer)
export default store
入口文件处引入状态管理器
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './15_redux_react-redux_combine/store'
import App from './15_redux_react-redux_combine/App'
import ErrorBoundary from './ErrorBoundary'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<ErrorBoundary>
<Provider store = { store }>
<App root={ root }/>
</Provider>
</ErrorBoundary>
)
组件中使用状态管理器
src/15_redux_react-redux_combine/App.jsx
// src/15_redux_react-redux_combine/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
const App = () => {
return (
<div>
<Home />
<Kind />
</div>
);
};
export default App;
src/15_redux_react-redux_combine/views/Home.jsx 业务逻辑在展示型组件中
// src/15_redux_react-redux_combine/views/Home.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Home = (props) => {
const { bannerList, proList } = props
useEffect(() => {
props.getBannerList()
}, [props])
return (
<div>
2222
{
bannerList && bannerList.map(item => {
return (
<img key = { item.bannerid } src={item.img} alt="" style={{ height: 40 }}/>
)
})
}
{
proList && proList.map(item => {
return (
<li key = { item.proid }> { item.proname } </li>
)
})
}
</div>
);
};
// state => { return {}} <===> state => ({})
// dispatch => { return {}} <===> dispatch => ({})
export default connect(state => ({ // state = { home: { bannerList: [], proList: []}, kind: { kindList: []}}
bannerList: state.home.bannerList,
proList: state.home.proList
}), dispatch => ({
getBannerList () {
fetch('http://121.89.205.189:3000/api/banner/list')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_BANNER_LIST',
payload: res.data
})
})
fetch('http://121.89.205.189:3000/api/pro/list')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_PRO_LIST',
payload: res.data
})
})
}
}))(Home);
src/15_redux_react-redux_combine/views/Kind.jsx 业务逻辑在容器组件中
// src/15_redux_react-redux_combine/views/Kind.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Kind = (props) => {
const { kindList } = props
useEffect(() => {
props.getKindList()
}, [props])
return (
<div>
333
{
kindList && kindList.map(item => {
return (
<p key = { item }>{ item }</p>
)
})
}
</div>
);
};
// (state) => { return { bannerList: state.bannerList, proList: state.proList }}
// (state) => ({ bannerList: state.bannerList, proList: state.proList })
// ({ bannerList, proList }) => ({ bannerList: bannerList, proList: proList })
// ({ bannerList, proList }) => ({ bannerList, proList })
// state => ({ kindList: state.kind.kindList })
// ({ kind }) => ({ kindList: kind.kindList })
// ({ kind: { kindList } }) => ({ kindList: kindList })
export default connect(({ kind: { kindList }}) => ({ kindList }), dispatch => ({
getKindList () {
fetch('http://121.89.205.189:3000/api/pro/categorylist')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_KIND_LIST',
payload: res.data
})
})
}
}))(Kind);
细心的你发现,虽然展示组件的代码变少了,但是容器组件中还有 异步相关操作,能否把这些异步操作提取出去
18.6 redux + react-redux + 分模块+ 异步操作 - 了解
配合redux 常用的异步模块 为
redux-thunk,redux-saga
18.6.1 redux-thunk
$ cnpm i redux-thunk -S
src/16_redux_react-redux_redux-thunk_combine/store/modules/home.js
// src/16_redux_react-redux_redux-thunk_combine/store/modules/home.js
const reducer = (state = {
bannerList: [],
proList: []
}, { type, payload }) => {
switch (type) {
case 'CHANGE_BANNER_LIST':
return { ...state, bannerList: payload }
case 'CHANGE_PRO_LIST':
return { ...state, proList: payload }
default:
return state
}
}
export default reducer
src/16_redux_react-redux_redux-thunk_combine/store/modules/kind.js
// src/16_redux_react-redux_redux-thunk_combine/store/modules/kind.js
const reducer = (state = {
kindList: []
}, { type, payload }) => {
switch (type) {
case 'CHANGE_KIND_LIST':
return { ...state, kindList: payload }
default:
return state
}
}
export default reducer
整合多个reducer 为一个,因为 有一个原则 : 单一数据源 的原则
并且后续需要使用 异步操作,将异步操作模块 使用进来
src/16_redux_react-redux_redux-thunk_combine/store/index.js
// src/16_redux_react-redux_redux-thunk_combine/store/index.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk' // 异步
import home from './modules/home'
import kind from './modules/kind'
const reducer = combineReducers({ home, kind })
const store = createStore(reducer, applyMiddleware(thunk))
export default store
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './16_redux_react-redux_redux-thunk_combine/store'
import App from './16_redux_react-redux_redux-thunk_combine/App'
import ErrorBoundary from './ErrorBoundary'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<ErrorBoundary>
<Provider store = { store }>
<App root={ root }/>
</Provider>
</ErrorBoundary>
)
src/16_redux_react-redux_redux-thunk_combine/App.jsx
// src/16_redux_react-redux_redux-thunk_combine/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
const App = () => {
return (
<div>
<Home />
<Kind />
</div>
);
};
export default App;
上一个案例 异步操作在组件,现在需要将其放到 异步操作模块中去 actionCreator
src/16_redux_react-redux_redux-thunk_combine/store/actions/home.js
// src/16_redux_react-redux_redux-thunk_combine/store/actions/home.js
const action = {
getBannerListData (dispatch) {// 函数的默认参数
fetch('http://121.89.205.189:3000/api/banner/list')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_BANNER_LIST',
payload: res.data
})
})
},
// getProListData (dispatch) {
// fetch('http://121.89.205.189:3000/api/pro/list')
// .then(res => res.json())
// .then(res => {
// dispatch({
// type: 'CHANGE_PRO_LIST',
// payload: res.data
// })
// })
// }
// { count: 1 , limitNum: 10}
getProListData (params) {// 需要传递参数时,返回dispatch的默认参数的函数
return (dispatch) => {
fetch('http://121.89.205.189:3000/api/pro/list?limitNum=' + params.limitNum)
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_PRO_LIST',
payload: res.data
})
})
}
}
}
export default action
src/16_redux_react-redux_redux-thunk_combine/store/actions/kind.js
// src/16_redux_react-redux_redux-thunk_combine/store/actions/kind.js
const action = {
getKindListData (dispatch) {
fetch('http://121.89.205.189:3000/api/pro/categorylist')
.then(res => res.json())
.then(res => {
dispatch({
type: 'CHANGE_KIND_LIST',
payload: res.data
})
})
}
}
export default action
记住接口有无参数影响 action 的写法,还影响其调用
src/16_redux_react-redux_redux-thunk_combine/views/Home.jsx
// src/16_redux_react-redux_redux-thunk_combine/views/Home.jsx
import React, { useEffect} from 'react';
import { connect } from 'react-redux'
import action from '../store/actions/home'
const Home = (props) => {
const { bannerList, proList, getBannerList, getProList } = props
useEffect(() => {
getBannerList()
getProList()
}, [getBannerList, getProList])
return (
<div>
{
bannerList && bannerList.map(item => {
return (
<img key = { item.bannerid } src={item.img} alt="" style={{ height: 40 }}/>
)
})
}
{
proList && proList.map(item => {
return (
<li key = { item.proid }> { item.proname } </li>
)
})
}
</div>
);
};
export default connect(({ home: { bannerList, proList }})=> ({ bannerList, proList }), dispatch => {
return {
getBannerList () {
// 触发异步操作
dispatch(action.getBannerListData)
},
getProList () {
dispatch(action.getProListData({ limitNum: 5 }))
}
}
})(Home);
src/16_redux_react-redux_redux-thunk_combine/views/Kind.jsx 异步在store
// src/16_redux_react-redux_redux-thunk_combine/views/Kind.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
import action from '../store/actions/kind'
const Kind = (props) => {
const { kindList, getKindList } = props
useEffect(() => {
getKindList()
}, [getKindList])
return (
<div>
{
kindList && kindList.map(item => {
return (
<p key = { item }>{ item }</p>
)
})
}
</div>
);
};
export default connect(({ kind: { kindList }}) => ({ kindList }), dispatch => ({
getKindList () {
dispatch(action.getKindListData)
}
}))(Kind);
虽然引入了redux-thunk,但是仍然可以把 异步放在组件中,具体根据项目的需求而定
创建状态模块
整合模块
创建异步的 actionCreator
组件触发 actionCreator
传递数据以及修改界面
18.6.2 redux-saga
先要了解解决异步操作方案:回调函数、promise、async await、generator yield
es6.ruanyifeng.com/#docs/gener…
$ cnpm i redux-saga -S
src/17_redux_react-redux_redux-saga_combine/store/modules/home.js
// src/17_redux_react-redux_redux-saga_combine/store/modules/home.js
const reducer = (state = {
bannerList: [],
proList: []
}, { type, payload }) => {
switch (type) {
case 'CHANGE_BANNER_LIST':
return { ...state, bannerList: payload }
case 'CHANGE_PRO_LIST':
return { ...state, proList: payload }
default:
return state
}
}
export default reducer
src/17_redux_react-redux_redux-saga_combine/store/modules/kind.js
// src/17_redux_react-redux_redux-saga_combine/store/modules/kind.js
const reducer = (state = {
kindList: []
}, { type, payload }) => {
switch (type) {
case 'CHANGE_KIND_LIST':
return { ...state, kindList: payload }
default:
return state
}
}
export default reducer
src/17_redux_react-redux_redux-saga_combine/store/mySaga.js
// src/17_redux_react-redux_redux-saga_combine/store/mySaga.js
// call 用来触发异步请求
// put 理解为 原来 store.dispatch
// takeLatest 用来分发告诉程序执行哪一个异步行为
import { call, put, takeLatest } from 'redux-saga/effects'
function getBannerListData () {
return fetch('http://121.89.205.189:3000/api/banner/list').then(res => res.json())
}
function getProListData (params) {
console.log(params)
return fetch('http://121.89.205.189:3000/api/pro/list').then(res => res.json())
}
function getKindListData () {
return fetch('http://121.89.205.189:3000/api/pro/categorylist').then(res => res.json())
}
function * getBannerListAction () { // * 代表generator函数,函数内部使用yield继续执行
const res = yield call(getBannerListData)
yield put({
type: 'CHANGE_BANNER_LIST',
payload: res.data
})
}
function * getProListAction (action) { // * 代表generator函数,函数内部使用yield继续执行, action理解为参数
const res = yield call(getProListData, action.payload) // action.payload 即为参数
yield put({
type: 'CHANGE_PRO_LIST',
payload: res.data
})
}
function * getKindListAction () { // * 代表generator函数,函数内部使用yield继续执行
const res = yield call(getKindListData)
yield put({
type: 'CHANGE_KIND_LIST',
payload: res.data
})
}
// 分发异步操作
function * mySaga () {
// takeLatest 第一个参数调用标识,第二个参数是一个函数
// 有人触发 REQUEST_BANNER_LIST 标识,那么调用 getBannerListAction 函数,不要()
yield takeLatest('REQUEST_BANNER_LIST', getBannerListAction)
yield takeLatest('REQUEST_PRO_LIST', getProListAction)
yield takeLatest('REQUEST_KIND_LIST', getKindListAction)
}
export default mySaga
src/17_redux_react-redux_redux-saga_combine/store/index.js
// src/17_redux_react-redux_redux-saga_combine/store/index.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import mySaga from './mySaga'
import home from './modules/home'
import kind from './modules/kind'
const reducer = combineReducers({ home, kind })
const middleware = createSagaMiddleware()
const store = createStore(reducer, applyMiddleware(middleware))
middleware.run(mySaga)
export default store
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './17_redux_react-redux_redux-saga_combine/store'
import App from './17_redux_react-redux_redux-saga_combine/App'
import ErrorBoundary from './ErrorBoundary'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<ErrorBoundary>
<Provider store = { store }>
<App root={ root }/>
</Provider>
</ErrorBoundary>
)
// src/17_redux_react-redux_redux-saga_combine/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
const App = () => {
return (
<div>
<Home />
<Kind />
</div>
);
};
export default App;
src/17_redux_react-redux_redux-saga_combine/views/Home.jsx
// src/17_redux_react-redux_redux-saga_combine/views/Home.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Home = (props) => {
console.log(props)
const {bannerList, proList, dispatch} = props
useEffect(() => {
dispatch({
type: 'REQUEST_BANNER_LIST'
})
dispatch({
type: 'REQUEST_PRO_LIST',
payload: { limitNum: 2 }
})
}, [dispatch])
return (
<div>
{
bannerList && bannerList.map(item => {
return (
<img key = { item.bannerid } src={item.img} alt="" style={{ height: 40 }}/>
)
})
}
{
proList && proList.map(item => {
return (
<li key = { item.proid }> { item.proname } </li>
)
})
}
</div>
);
};
export default connect(state => {
return {
bannerList: state.home.bannerList,
proList: state.home.proList
}
})(Home);
src/17_redux_react-redux_redux-saga_combine/views/Kind.jsx
// src/17_redux_react-redux_redux-saga_combine/views/Kind.jsx
import React, { useEffect } from 'react';
import {connect} from 'react-redux'
const Kind = (props) => {
const { kindList, dispatch} = props
useEffect(() => {
dispatch({ type: 'REQUEST_KIND_LIST'})
}, [dispatch])
return (
<div>
{
kindList && kindList.map(item => {
return (
<p key = { item }>{ item }</p>
)
})
}
</div>
);
};
export default connect(({ kind: { kindList }}) => ({kindList}))(Kind);
18.7 使用immutable不可变数据数据结构 - 了解
immutable不可变的数据解构,意思就是一旦数据被创建,需要使用特殊的方法进行修改(修改过后的数据也是符合immutable数据解构的,他是一个全新的对象)
学习immutable: www.npmjs.com/package/imm…
$ cnpm i immutable redux-immutable -S
重点就是修改 reducer 的数据机构以及组件中获取状态的方式(整合reducer时使用 redux-immutable 提供的 combineReducers)
十九、redux toolkit
rtk
19.1 什么是Redux Toolkit
Redux Toolkit 是我们官方的,有观点的,开箱即用的高效 Redux 开发工具集。它旨在成为标准的 Redux 逻辑开发方式,我们强烈建议您使用它。
它包括几个实用程序功能,这些功能可以简化最常见场景下的 Redux 开发,包括配置 store、定义 reducer,不可变的更新逻辑、甚至可以立即创建整个状态的 “切片 slice”,而无需手动编写任何 action creator 或者 action type。它还包括使用最广泛的 Redux 插件,例如 Redux Thunk 用于异步逻辑,而 Reselect 用于编写选择器 selector 函数,因此你可以立即使用它们。
19.2 如何使用redux-toolkit
$ cnpm i @reduxjs/toolkit -S
19.2.1 创建状态管理器
src/15_rtk/store/index.js
// src/15_rtk/store/index.js
// 1.从 Redux Toolkit 引入 configureStore API。
import { configureStore } from '@reduxjs/toolkit'
// 2.我们从创建一个空的 Redux store 开始,并且导出它:
export default configureStore({
reducer: {}
})
19.2.2 入口文件配置状态管理器
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 3.引入状态管理器 以及 辅助的组件Provider
import { Provider } from 'react-redux'
import store from './15_rtk/store'
import App from './15_rtk/App'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
{/* 4.组件形式调用状态管理器 */}
<Provider store={ store }>
<App />
</Provider>
</React.StrictMode>
</ErrorBoundary>
);
19.2.3 创建分模块切片
创建 slice 需要一个字符串名称来标识切片、一个初始 state 以及一个或多个定义了该如何更新 state 的 reducer 函数。slice 创建后 ,我们可以导出 slice 中生成的 Redux action creators 和 reducer 函数。
src/18_rtk/store/modules/home.js
// src/15_rtk/store/modules/home.js
//5. 创建 Redux State Slice
// 5.1引入 createSlice API。
import { createSlice } from '@reduxjs/toolkit'
// 5.2创建 slice 需要一个字符串名称来标识切片、一个初始 state 以及一个或多个定义了该如何更新 state 的 reducer 函数。
export const homeSlice = createSlice({
name: 'homeList',
initialState: {
bannerList: [],
proList: []
},
reducers: {
changeBannerData (state, action) {
state.bannerList = action.payload
},
changeProData (state, action) {
state.proList = action.payload
}
}
})
// 5.3 slice 创建后 ,我们可以导出 slice 中生成的 Redux action creators 和 reducer 函数。
export const { changeBannerData, changeProData } = homeSlice.actions
export default homeSlice.reducer
src/18_rtk/store/modules/kind.js
// src/15_rtk/store/modules/kind.js
//5. 创建 Redux State Slice
// 5.1引入 createSlice API。
import { createSlice } from '@reduxjs/toolkit'
// 5.2创建 slice 需要一个字符串名称来标识切片、一个初始 state 以及一个或多个定义了该如何更新 state 的 reducer 函数。
export const kindSlice = createSlice({
name: 'kindList',
initialState: {
kindList: []
},
reducers: {
changeKindData (state, action) {
console.log(action) // { type: '', payload: []}
state.kindList = action.payload
}
}
})
console.log(kindSlice)
console.log(kindSlice.actions) // changeKindData
// 5.3 slice 创建后 ,我们可以导出 slice 中生成的 Redux action creators 和 reducer 函数。
export const { changeKindData } = kindSlice.actions
export default kindSlice.reducer
19.2.4 整合切片
src/18_rtk/store/index.js
// src/15_rtk/store/index.js
// 1. 从 Redux Toolkit 引入 configureStore API。
import { configureStore } from '@reduxjs/toolkit'
import kind from './modules/kind'
import home from './modules/home'
// 2.我们创建一个空的 Redux store ,并且导出它:
export default configureStore({
reducer: {
// 6.将 Slice Reducers 添加到 Store 中
kind,
home
}
})
19.2.5 组件中使用状态管理器
src/18_rtk/views/Home.jsx
// src/15_rtk/views/Home.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { changeBannerData, changeProData } from '../store/modules/home';
const Home = () => {
const bannerList = useSelector(state => state.home.bannerList)
const proList = useSelector(state => state.home.proList)
const dispatch = useDispatch()
useEffect(() => {
fetch('http://121.89.205.189:3000/api/banner/list')
.then(res => res.json())
.then(res => {
dispatch(changeBannerData(res.data))
})
fetch('http://121.89.205.189:3000/api/pro/list')
.then(res => res.json())
.then(res => {
dispatch(changeProData(res.data))
})
}, [dispatch])
return (
<div>
首页
{
bannerList && bannerList.map(item => {
return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
})
}
<ul>
{
proList && proList.map(item => {
return (
<li key = { item.proid }>{ item.proname }</li>
)
})
}
</ul>
</div>
);
};
export default Home;
src/18_rtk/views/Kind.jsx
// src/15_rtk/views/Kind.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { changeKindData } from './../store/modules/kind'
// 7.在 React 组件中使用 Redux 状态和操作
// 7.1 可以使用 React-Redux 钩子让 React 组件与 Redux store 交互。
// 我们可以使用 useSelector 从 store 中读取数据,使用 useDispatch dispatch actions。
const Kind = () => {
const kindList = useSelector(state => state.kind.kindList)
const dispatch = useDispatch()
useEffect(() => {
fetch('http://121.89.205.189:3000/api/pro/categorylist')
.then(res => res.json())
.then(res => {
dispatch(changeKindData(res.data))
})
}, [dispatch])
return (
<div>
分类
<div>
分类
<ul>
{
kindList && kindList.map((item, index) => {
return <li key = { index }>{ item }</li>
})
}
</ul>
</div>
</div>
);
};
export default Kind;
使用
configureStore创建 Redux store
configureStore接受reducer函数作为命名参数configureStore使用的好用的默认设置自动设置 store为 React 应用程序组件提供 Redux store
- 使用 React-Redux
<Provider>组件包裹你的<App />- 传递 Redux store 如
<Provider store={store}>使用
createSlice创建 Redux "slice" reducer
- 使用字符串名称、初始状态和命名的 reducers 函数调用“createSlice”
- Reducer 函数可以使用 Immer 来“改变”状态
- 导出生成的 slice reducer 和 action creators
在 React 组件中使用 React-Redux
useSelector/useDispatch钩子
- 使用
useSelector钩子从 store 中读取数据- 使用
useDispatch钩子获取dispatch函数,并根据需要 dispatch actions
此时发现,组件中的异步操作在 组件内部调用,接下来需要研究如何将异步操作从组件提取出来
19.3 提取异步操作
19.3.1 自己定义请求函数
src/19_rtk_async/api/home.js
// src/16_rtk_async/api/home.js
export function getBannerListDataAction () {
return fetch('http://121.89.205.189:3000/api/banner/list').then(res => res.json())
}
export function getProListDataAction () {
return fetch('http://121.89.205.189:3000/api/pro/list').then(res => res.json())
}
src/19_rtk_async/api/kind.js
// src/16_rtk_async/api/kind.js
export function getKindListDataAction () {
return fetch('http://121.89.205.189:3000/api/pro/categorylist').then(res => res.json())
}
src/19_rtk_async/store/modules/home.js
// src/16_rtk_async/store/modules/home.js
import { createSlice } from '@reduxjs/toolkit'
import { getBannerListDataAction, getProListDataAction } from './../../api/home'
export const homeSlice = createSlice({
name: 'home',
initialState: {
bannerList: [],
proList: []
},
reducers: {
changeBannerData (state, action) {
state.bannerList = action.payload
},
changeProData (state, { payload }) {
state.proList = payload
}
}
})
// 异步操作,默认参数为dispatch
export function getBannerListAction (dispatch) {
getBannerListDataAction().then(res => {
dispatch(changeBannerData(res.data))
})
}
// export function getProListAction (dispatch) {
// getProListDataAction().then(res => {
// dispatch(changeProData(res.data))
// })
// }
// 如果需要参数传递,请返回一个自带dispatch参数的函数
export function getProListAction (params) {
console.log(params)
return (dispatch) => {
getProListDataAction().then(res => {
dispatch(changeProData(res.data))
})
}
}
export const { changeBannerData, changeProData } = homeSlice.actions
export default homeSlice.reducer
src/19_rtk_async/store/index.js
// src/16_rtk_async/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import home from './modules/home'
import kind from './modules/kind'
export default configureStore({
reducer: {
home, kind
}
})
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 3.引入状态管理器 以及 辅助的组件Provider
import { Provider } from 'react-redux'
import store from './16_rtk_async/store'
import App from './16_rtk_async/App'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
{/* 4.组件形式调用状态管理器 */}
<Provider store={ store }>
<App />
</Provider>
</React.StrictMode>
</ErrorBoundary>
);
src/19_rtk_async/App.jsx
// src/16_rtk_async/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
const App = () => {
return (
<div>
<Home />
<hr />
<Kind />
</div>
);
};
export default App;
src/19_rtk_async/views/Home.jsx
// src/16_rtk_async/views/Home.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getBannerListAction, getProListAction } from '../store/modules/home'
const Home = () => {
const bannerList = useSelector(state => state.home.bannerList)
const proList = useSelector(state => state.home.proList)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getBannerListAction) // 触发 异步行为,主要不要随意加()
// dispatch(getProListAction)
dispatch(getProListAction({ limitNum: 2 }))
}, [dispatch])
return (
<div>
首页
{
bannerList && bannerList.map(item => {
return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
})
}
<ul>
{
proList && proList.map(item => {
return (
<li key = { item.proid }>{ item.proname }</li>
)
})
}
</ul>
</div>
);
};
export default Home;
19.3.2 使用 createAsyncThunk 请求数据 - 了解
Redux Toolkit 的 createAsyncThunk API 生成 thunk,为您自动 dispatch 那些 "start/success/failure" action。
src/19_rtk_async/store/modules/kind.js
// src/16_rtk_async/store/modules/kind.js
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { getKindListDataAction } from '../../api/kind'
export const kindSlice = createSlice({
name: 'kind',
initialState: {
kindList: []
},
reducers: {
changeKindData (state, action) {
state.kindList = action.payload
}
},
extraReducers(builder) { // 固定结构
builder
.addCase(getKindListAction.pending, (state, action) => {
state.status = 'loading'
})
.addCase(getKindListAction.fulfilled, (state, action) => {
state.status = 'succeeded'
// Add any fetched posts to the array
state.kindList = action.payload
})
.addCase(getKindListAction.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
// createAsyncThunk 两个参数,第一个自定义
// 第二个参数请求数据
export const getKindListAction = createAsyncThunk('kind/getKindListAction', async () => {
const res = await getKindListDataAction()
return res.data
})
export const { changeKindData } = kindSlice.actions
export default kindSlice.reducer
// src/19_rtk_async/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import home from './modules/home'
import kind from './modules/kind'
export default configureStore({
reducer: {
home,
kind
}
})
src/19_rtk_async/views/Kind.jsx
// src/16_rtk_async/views/Kind.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getKindListAction } from '../store/modules/kind';
const Kind = () => {
const kindList = useSelector(state => state.kind.kindList)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getKindListAction()) // 触发异步操作
}, [dispatch])
return (
<div>
分类
<ul>
{
kindList && kindList.map((item, index) => {
return <li key = { index }>{ item }</li>
})
}
</ul>
</div>
);
};
export default Kind;
redux
Redux + redux-thunk
redux + react-redux
Redux + react-redux 分模块
redux + react-redux + redux-thunk
redux + react-redux + redux-thunk + 分模块
redux + react-redux + redux-saga
redux + react-redux + redux-saga + 分模块
redux-toolkit
(Redux-thunk\redux-saga\redux-promise....)