Redux in class
Redux 是一个帮助我们管理 State 的容器:Redux 是 JS 的状态容器,提供了可预测的状态管理。
yarn add redux
Store
Store 就是用来维持应用所有 state 树的一个对象。改变 store 内 state 的唯一途径是对它 dispatch 一个 action。
const initialState = {
friends: [
{ name: 'Sytus', age: 17 },
{ name: 'Ram', age: 18 },
{ name: 'You', age: 20 },
]
}
action
Redux 要求我们通过 action 来更新数据:
所有数据的变化,必须通过派发(dispatch)action 来更新;
action 是一个普通的 JS 对象,用来描述这次更新的 type 和 content。
强制使用 action 的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可追踪、可预测的;
下面案例中的 action 是固定的对象,真实应用中,我们会通过函数来定义,返回一个 action。
const action1 = { type: 'ADD_FRIEND', info: { name: 'lucy', age: 20 } }
const action2 = { type: 'INC_AGE', index: 0 }
const action3 = { type: 'CHANGE_NAME', playload: { index: 0, newName: 'Sytus' } }
reducer
Reducer 将 state 和 action 联系在一起。
Reducer 是一个纯函数;
Reducer 做的事情就是将传入的 state 和 action 结合起来生成一个新的 state。
const redux = require('redux') // 引入redux
const initialState = {
counter: 0,
}
// reducer
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + 1 }
case 'DECREMENT':
return { ...state, counter: state.counter - 1 }
case 'ADD_NUMBER':
return { ...state, counter: state.counter + action.num }
case 'SUB_NUMBER':
return { ...state, counter: state.counter - action.num }
default:
return state
}
}
// store(创建的时候需要传入一个reducer)
const store = redux.createStore(reducer)
// 订阅store修改
store.subscribe(() => {
console.log('counter:', store.getState().counter);
})
// action
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'DECREMENT' }
const action3 = { type: 'ADD_NUMBER', num: 5 }
const action4 = { type: 'SUB_NUMBER', num: 12 }
// dispatch
store.dispatch(action1) // 执行reducer函数
store.dispatch(action2)
store.dispatch(action3)
store.dispatch(action4)
// 最终打印:
// counter: 1
// counter: 0
// counter: 5
// counter: -7
Redux的三大原则
单一数据源
整个应用程序的 state 都储存在一颗 object tree 中,并且这个 object tree 只储存在一个 store 中;
Redux 并没有强制让我们不能创建多个 Store,但是那样并不利于数据的维护;
单一的数据源可以让整个应用程序变得方便维护、追踪、修改。
State 是只读的
唯一修改 State 的方法一定是触发 action,不要试图在其他地方通过任何方式来修改 State;
确保 View 或网络请求都不能直接修改 state,它们只能通过 action 来描述自己想要如何修改 state;
保证所有修改被集中化处理,并且按照严格的顺序来执行,所以不需要担心 race condition(竟态)的问题。
使用纯函数来执行修改
通过 reducer 将旧 state 和 action 联系在一起,并且返回一个新的 state;
随着应用程序的复杂度增加,我们可以将 reducer 拆分成多个小的 reducers,分别操作不同的 state tree 的一部分;
但所有的 reducer 都应该是纯函数,不能产生任何的副作用。
纯函数:
函数的返回结果只依赖于它的参数;
函数执行过程里面没有副作用。
let foo=(a, b)=>a+b foo(1,2) //=3
副作用:
- 除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API 修改页面,或者你发送了 AJAX 请求,还有调用 window.reload 刷新浏览器,甚至是 console.log 往控制台打印数据也是副作用。
开发事项
文件处理
在正常的开发中,我们会把 redux 的相关代码放进一个命名为“store”的文件夹中;
store文件夹下我们会创建以下的文件:
- index.js:用于创建 store
import redux from 'redux' import reducer from './reducer.js' const store = redux.createStore(reducer) export default store
- reducer.js:用于创建 reducer
import { ADD_NUMBER, SUB_NUMBER } from './constants.js' const defaultState = { counter: 0 } function reducer(state = defaultState, action) { switch (action.type) { case ADD_NUMBER: return { ...state, counter: state.counter + action.num } case SUB_NUMBER: return { ...state, counter: state.counter - action.num } default: return state } } export default reducer
- actionCreators.js:用于创建 action
import { ADD_NUMBER, SUB_NUMBER } from './constants.js' export const addAction = num => ({ type: ADD_NUMBER, num }) export const subAction = num => ({ type: SUB_NUMBER, num })
- constants.js:用于创建一些我们容易写错的值
export const ADD_NUMBER = 'ADD_NUMBER' export const SUB_NUMBER = 'SUB_NUMBER'
使用高阶组件简化代码
connect.js
import React, {PureComponent} from 'react'
import store from '../store'
export function connect(mapStateToProps, mapDispatchToProps) {
return function enhanceHOC(WrappedComponent) {
return class extends PureComponent {
constructor(props) {
super(props)
this.state = {
storeState: mapStateToProps(store.getState())
}
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.setState({
storeState: mapStateToProps(store.getState())
})
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
return <WrappedComponent {...this.props}
{...mapStateToProps(store.getState())}
{...mapDispatchToProps(store.dispatch)} />
}
}
}
}
目标组件
import React from 'react'
import {
addAction
} from '../store/actionCreators.js'
import { connect } from '../utils/connect'
function Home(props) {
return (
<div>
<h1>Home</h1>
<h2>当前计数:{props.counter}</h2>
<button onClick={e => { props.increment() }}>+1</button>
<button onClick={e => { props.addNumber(5) }}>+5</button>
</div>
)
}
const mapStateToProps = state => {
return {
counter: state.counter
}
}
const mapDispatchToProps = dispatch => {
return {
increment: function() {
dispatch(addAction(1))
},
addNumber: function(num) {
dispatch(addAction(num))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
使用redux库
yarn add react-redux
- 项目入口 index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App'
import store from './store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
- 在目标文件中更改 connect 导入即可:
// import { connect } from '../utils/connect'
import {connect} from 'react-redux'
redux中异步操作
在 redux 中,我们可以使用中间件(Middleware)进行异步操作。
它可以让我们在 dispatch actions 和 reducer 处理之间进行异步操作(例“网络请求”)。
redux-thunk
- 安装
yarn add redux-thunk
在创建 store 时传入应用了 middleware 的 enhance 函数
通过 applyMiddleware 来结合多个 Middleware,返回一个 enhancer;
将 enhancer 作为第二个参数传入到 createStore 中;
import {createStore, applyMiddleware} from 'redux'
import thunkMiddleware from 'redux-thunk'
const enhancer = applyMiddleware(thunkMiddleware)
const store = createStore(reducer, enhancer)
定义返回一个函数的 action
注意:这里不是返回一个对象了,而是一个函数;
该函数在 dispatch 之后会被执行;
const getHomeMultidataAction = (dispatch, getState) => {
axios.get("https://...").then(res => {
const data = res.data.data
dispatch(changeBannersAction(data.banner.list))
})
}
Redux in function
在 React 中使用 redux,官方要求安装两个其他插件 - Redux Toolkit 和 react-redux
Redux Toolkit(RTK):官方推荐编写 Redux 逻辑的方式,是一套工具的集合集,简化书写方式;
简化 store 的配置方式;
内置 immer 支持可变式状态修改;
内置 thunk 更好的异步创建。
React-redux - 用来链接 Redux 和 React 组件的中间件
yarn add @reduxjs/toolkit react-redux
React-redux 负责把 Redux 和 React 链接起来,内置 Provider 组件通过 store 参数把创建好的 store 实例注入到应用中,链接正式建立。
参考文档:redux最佳实践redux-toolkit使用指南
Store的目录设计
在 src 文件夹下创建 store 文件夹;
应用通常会有多个 store 模块,所以创建一个 modules 文件夹,在其内部编写业务分类的子 store;
Store 中的入口文件 index.js 的作用是组合 modules 中所有的子模块,并导出 store。
文件编写
- counterStore
import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// 初始化state
initialState: {
count: 0
},
// 修改状态的方法 同步方法 支持直接修改
reducers: {
// state 就是 initialState
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
}
})
// 结构actionCreater函数
const {increment, decrement} = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer
// 以按需导出的方式导出actionCreater
export {increment, decrement}
// 以默认导出的方式导出reducer
export default reducer
- index.js
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块reducer
import counterReducer from './modules/counterStore'
const store = configureStore({
reducer: {
counter: counterReducer
}
})
export default store
React组件使用store中的数据
在 React 组件中使用 store 中的数据。需要用到一个钩子函数 useSelector,它的作用是把 store 中的数据映射到组件中。
import React from 'react'
import { useSelector } from 'react-redux';
function App() {
const {count} = useSelector(state => state.counter)
return (
<div>
App
{count}
</div>
);
}
export default App;
React组件修改store中的数据
React 组件中修改 store 中的数据需要借助另外一个 hook 函数 useDispatch,它的作用是生成提交 action 对象的 dispatch 函数。
import React from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store/modules/counterStore';
function App() {
const { count } = useSelector(state => state.counter)
const dispatch = useDispatch()
return (
<div>
{count}
<hr />
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
export default App;
提交action传参
在 reducers 的同步修改方法中添加 action 对象参数,在调用 actionCreater 的时候传递参数,参数会被传递到 action 对象 payload 属性上。
Payload 只能收到一个参数,且不能是对象,但我们可以通过数组传递多个参数。
// counterStore
import { createSlice } from "@reduxjs/toolkit"
import { act } from "react-dom/test-utils"
// const {createSlice} = require('@reduxjs/toolkit')
const counterStore = createSlice({
name: 'counter',
// 初始化state
initialState: {
count: 0
},
// 修改状态的方法 同步方法 支持直接修改
reducers: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
},
addToNum(state, action) {
state.count = (state.count
+ action.payload[0]
+ action.payload[1]
+ action.payload[2].a
+ action.payload[2].b)
}
}
})
// 结构actionCreater函数
const { increment, decrement, addToNum } = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer
// 以按需导出的方式导出actionCreater
export { increment, decrement, addToNum }
// 以默认导出的方式导出reducer
export default reducer
// app.js
import React from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, addToNum } from './store/modules/counterStore';
function App() {
const { count } = useSelector(state => state.counter)
const dispatch = useDispatch()
return (
<div>
{count}
<hr />
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={
() => dispatch(addToNum([5, 10, {a: 2, b: 3}]))
}>+20</button>
</div>
);
}
export default App;
异步状态操作
单独封装一个函数,在函数内部 return 一个新函数,在新函数中:
封装异步请求获取数据
调用同步 actionCreater 传入异步数据生成一个 action 对象,并使用 dispatch 提交
// 结构actionCreater const { setChannels } = channelStore.actions // 异步请求 const url = 'http://geek.itheima.net/v1_0/channels' const fetchChannel = () => { return async (dispatch) => { const res = await axios.get(url) dispatch(setChannels(res.data.data.channels)) } }
各文件代码
channelStore
// channelStore
import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"
const channelStore = createSlice({
name: 'channel',
initialState: {
channelList: [],
},
reducers: {
setChannels(state, action) {
state.channelList = action.payload
},
}
})
// 结构actionCreater
const { setChannels } = channelStore.actions
// 异步请求
const url = 'http://geek.itheima.net/v1_0/channels'
const fetchChannel = () => {
return async (dispatch) => {
const res = await axios.get(url)
dispatch(setChannels(res.data.data.channels))
}
}
export { fetchChannel }
const reducer = channelStore.reducer
export default reducer
入口文件
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块reducer
import channelReducer from './modules/channelStore'
const store = configureStore({
reducer: {
channel: channelReducer
}
})
export default store
App
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { fetchChannel } from './store/modules/channelStore';
function App() {
const { channelList } = useSelector(state => state.channel)
// state 来源于store入口文件的reducer
// 结构出来的参数来源于 Stroe 里的 initialState 中的数据
const dispatch = useDispatch()
// 使用useEffect触发异步请求执行
useEffect(() => {
dispatch(fetchChannel())
console.log(dispatch);
}, [dispatch])
return (
<div>
<ul>
{channelList.map(item => (<li key={item.id}>{item.name}</li>))}
</ul>
</div>
);
}
export default App;