redux和react-redux笔记,以及项目中如何使用,对redux的封装,让其使用类似于vuex一样方便。
一、redux
1. redux工作流程
流程:创建action
=> dispatch
分发action
=> 交给store
=> reducer
加工数据返回给store
2. redux的三个核心概念
2.1 action
动作对象,包含两个属性:
- type:标识属性,值为
string
,唯一,必要属性 - data:数据属性,值类型任意,可选属性
{ type: 'userInfo', data: { name: 'xiaotian', age: 20 } }
2.2 reducer
用于初始化状态、加工状态。加工时,根据旧的state
和action
,产生新的state
的纯函数。redux的reducer函数必须是一个纯函数。
纯函数:同样的输入,同样的输出
遵循:
- 不得改写参数数据
- 不会产生任何副作用,例如:网络请求,输入输出设备
- 不能调用
Date.now()
或者Math.random()
等不纯的方法例如:下面这个函数就不是纯函数
// 传入同样的参数num,每次返回的结果都不相同,所以不是纯函数。 function fn(num) { return Math.random() + num }
错误写法:
let personList = []
export default function personReducer(preState = personList, action) {
const { type, data } = action
switch (type) {
case 'AddPerson':
// 不能这样写,会导致personList被改写了,personReducer就不是一个纯函数了,会导致reducx不能识别到数据的改变。
personList.unshift(data)
return personList
default:
return preState
}
}
正确写法:
let personList = []
export default function countReducer(preState = personList, action) {
const { type, data } = action
switch (type) {
case 'AddPerson':
return [data, ...personList]
default:
return preState
}
}
2.3 store
用于存储数据,有如下方法:
-
getState()
: 获取当前的state对象。 -
dispatch(action)
: 分发action,这是改变state的唯一途径。每个action是一个描述“发生了什么”的普通JavaScript对象。 -
subscribe(() => {})
: 注册一个监听器,当state发生改变时,会调用该回调。
3. redux使用
3.1 安装redux
npm install redux
3.2 基本使用
- 新建
redux
目录,在redux
目录下新建store.js
和xxx.js
store.js
// 引入legacy_createStore,专门用于创建redux中最核心的store对象
import { legacy_createStore } from 'redux'
// 引入为xxx组件服务的reducer
import countReducer from './count_reducer'
// 用于暴露一个store对象,整个应用只有一个store
export default legacy_createStore(countReducer)
xxx.js
(这里是count_reducer.js
)
创建一个为xxx组件服务的reducer,reducer的本质就是一个函数
countReducer有两个参数:preState(之前的状态)、action(动作对象)
reducer第一次调用时,是store自动触发的,传递的preState是undefined,action是@@REDUX/INITxxx
export default function countReducer(preState = 0, action) {
// 从action对象中获取:type、data
const { type, data } = action
// 根据type决定如何加工数据
switch (type) {
case 'increment':
return preState + data
case 'decrement':
return preState - data
default:
return preState
}
}
getState
:获取store
中的state
数据
store.getState()
dispatch
:store
派发数据(更新数据)
store.dispatch({ type: 'increment', data: xxx })
subscribe
:监听store
中的数据变化
store.subscribe(() => {
// 调用render,渲染页面
})
组件中使用:
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}
全局index.js
中使用:
import store from './redux/store'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
store.subscribe(() => {
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
})
3.3 异步action
- action分类:
- 同步action:一般对象类型,
{type: string, data: any}
- 异步action:函数类型
- 异步action的使用:
组件中:
import React, { Component } from 'react'
import store from '../redux/store'
import { createIncrementAction, createAsyncIncrementAction } from '../redux/count_action'
export default class count extends Component {
handleAsyncIncrement = () => {
const { value } = this.selectNumber
store.dispatch(createAsyncIncrementAction(value))
}
render() {
return (
<div>
<h1>当前求和为: {store.getState()}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.handleAsyncIncrement}>异步加</button>
</div>
)
}
}
count_action.js中(用于创建action对象):
// 异步action:指action的值是函数,异步action中都会调用同步action去真正的操作数据
export const createAsyncIncrementAction = (data) => {
return (dispatch) => {
// 由redux调用,传递参数dispatch
setTimeout(() => {
dispatch({ type: 'increment', data: data * 1 })
}, 1000)
}
}
⚠️注意:redux的action默认是不支持函数类型的,需要使用中间件
redux-thunk
。安装:
npm install redux-thunk
store.js中:
import { legacy_createStore, applyMiddleware } from 'redux'
// 引入redux-thunk,用于支持异步action
import { thunk } from 'redux-thunk'
import countReducer from './count_reducer'
export default legacy_createStore(countReducer, applyMiddleware(thunk))
这样就可以使用异步action了。
二、react-redux(类式组件中使用)
react-redux搭配redux使用步骤:
1. 目录结构
- components: 存放UI组件
- containers: 存放容器组件
- redux: 存放redux仓库
2. react-redux的基本使用
2.1 redux
store.js: 创建store
import { legacy_createStore, applyMiddleware } from 'redux'
import count_reducer from './count_reducer'
import { thunk } from 'redux-thunk'
export default legacy_createStore(count_reducer, applyMiddleware(thunk))
count_reducer.js
export default function countReducer(preState = 0, action) {
const { type, data } = action
switch (type) {
case 'ADD':
return preState + data
default:
return preState
}
}
2.2 containers(容器组件): containers/count.jsx
容器组件的store是靠父组件的props传递进去的(2.3的App.jsx),而不是自己import引入的
// 引入cont的ui组件
import CountUI from '../components/count'
// 引入connect用于连接ui组件和redux
import { connect } from 'react-redux'
// 1. mapStateToProps函数的返回的是一个对象
// 2. 返回对象中的key就作为传递给ui组件的props的key,value就作为传递给ui组件props的value
// 3. mapStateToProps函数的本质:传递状态,把状态传递给props
function mapStateToProps(state) {
return {
count: state
}
}
// 1. mapDispatchToProps函数的返回的是一个对象
// 2. 返回对象中的key就作为传递给ui组件的props的key,value就作为传递给ui组件props的value
// 3. mapDispatchToProps函数的本质:传递操作状态的方法
function mapDispatchToProps(dispatch) {
return {
add: () => {
console.log('add')
dispatch({ type: 'ADD', data: 1 })
}
}
}
// 使用connect()()创建并暴露一CountUI的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
2.3 App.jsx
⚠️注意:这里引入的Count组件是容器组件(2.2 创建的),而不是UI组件
通过props的方式,将store传入给容器组件,在mapStateToProps
中就可以获取到state
,mapDispatchToProps
中可获取到dispatch
import React, { Component } from 'react'
import Count from './containers/count'
import store from './redux/store'
export default class App extends Component {
render() {
return <>
<div>
<h1>App</h1>
<Count store={store} />
</div>
</>
}
}
2.4 components(UI组件): components/count.jsx
这个时候通过this.props
即可获取redux的store仓库中数据和方法(在2.2容器组件定义的数据和方法)
import React, { Component } from 'react'
export default class count extends Component {
handleAdd = () => {
this.props.add()
}
render() {
// console.log('ui组件props', this.props)
return (
<div>
<p>num为: {this.props.count}</p>
<button onClick={this.handleAdd}>+1</button>
</div>
)
}
}
3. 优化
3.1 优化1:容器组件mapDispatchToProps
的简写
mapDispatchToProps可以是函数,也可以是对象
value只要返回action,react-redux自动dispatch分发action
// 引入cont的ui组件
import CountUI from '../components/count'
// 引入connect用于连接ui组件和redux
import { connect } from 'react-redux'
export default connect(
state => ({
count: state
}),
// mapDispatchToProps的简写
{
add: () => ({ type: 'ADD', data: 1 })
}
)(CountUI)
3.2 优化2:不用再手动监测redux数据的变化,react-redux自动监测
3.3 优化3:Provider使用
如果有多个容器组件,需要每个容器组件都传入store,就类似这样:
<Count store={store} />
<Demo store={store} />
<Text store={store} />
优化:可以通过Provider
向每个容器组件都传入store,在入口文件中如下使用:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { Provider } from 'react-redux'
import store from './redux/store.js'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
)
需要组件的地方直接使用即可:
<Count />
3.4 整合:UI组件+容器组件
import React, { Component } from 'react'
import { connect } from 'react-redux'
// ui组件
class count extends Component {
handleAdd = () => {
this.props.add()
}
render() {
return (
<div>
<p>num为: {this.props.count}</p>
<button onClick={this.handleAdd}>+1</button>
</div>
)
}
}
// 容器组件
export default connect(
state => ({
count: state
}),
{
add: () => ({ type: 'ADD', data: 1 })
}
)(count)
4. 多个reducer的使用
store.js中:
import { legacy_createStore, applyMiddleware, combineReducers } from 'redux'
import count_reducer from './reducers/count'
import person_redercer from './reducers/person'
import { thunk } from 'redux-thunk'
// 汇总所有的reducer变成一个总的reducer
const allReducer = combineReducers({
count: count_reducer,
personList: person_redercer
})
export default legacy_createStore(allReducer, applyMiddleware(thunk))
容器组件中:
import React, { Component } from 'react'
import { connect } from 'react-redux'
class count extends Component {
handleAdd = () => {
// 操作状态的方法不需要区分
this.props.add()
}
render() {
return (
<div>
<p>num为: {this.props.count}</p>
<button onClick={this.handleAdd}>+1</button>
<div>{this.props.personList.length}</div>
</div>
)
}
}
export default connect(
state => ({
// state.xxx:xxx是根据store.js中的allReducer中的key值决定的
count: state.count,
personList: state.personList
}),
{
add: () => ({ type: 'ADD', data: 1 })
}
)(count)
5. redux开发者工具的使用
5.1 下载浏览器插件
下载地址:https://chrome.zzzmh.cn/info/lmhkpmbekcpmknklioeibfkpmmfibljd
5.2 项目中安装
npm i redux-devtools-extension
5.3 在store.js中配置
import { legacy_createStore, applyMiddleware } from 'redux'
import count_reducer from './reducers/count'
import { thunk } from 'redux-thunk'
// 引入开发者工具
import { composeWithDevTools } from 'redux-devtools-extension'
export default legacy_createStore(count_reducer, composeWithDevTools(applyMiddleware(thunk)))
这样浏览器插件就会亮了。
三、react-redux(函数式组件使用)
1. 可以通过UI组件和容器组件结合的写法:
import { connect } from 'react-redux'
const CountUI = (props) => {
return (
<div>
<h1>当前求和为:{props.count}</h1>
<button onClick={props.add}>+1</button>
</div>
)
}
function mapStateToProps(state) {
return {
count: state
}
}
function mapDispatchToProps(dispatch) {
return {
add: () => {
console.log('add')
dispatch({ type: 'ADD', data: 1 })
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
2. Hooks写法(推荐)
2.1 创建仓库
import { legacy_createStore } from 'redux'
import reducer from './reducer'
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 配置redux-devtools
const store = legacy_createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
export default store
2.2 创建reducer(初始化仓库数据,和修改仓库数据)
// 仓库数据
const defaultState = {
num: 0
}
// 当调用dispatch的时候会触发函数
let reducer = (state = defaultState, action) => {
// action:dispatch传递过来的对象
// 对数据进行深拷贝
let newState = JSON.parse(JSON.stringify(state))
switch (action.type) {
case 'ADD1':
newState.num += 1
break;
case 'ADD2':
newState.num -= action.num
break;
default:
break;
}
return newState
}
export default reducer
2.3 在main.js
中将store与项目关联
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { Provider } from 'react-redux'
import store from './store/index.js'
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
)
2.4 在组件中获取仓库数据和修改仓库数据(2.2关联)
- 获取仓库数据:通过
useSelector
获取 - 修改仓库数据:通过
useDispatch
获取
import { useSelector, useDispatch } from "react-redux"
export default () => {
// 通过useSelector获取仓库数据,回调返回一个对象或数组
const { num } = useSelector((state) => {
return {
num: state.num
}
})
// 修改仓库数据
const dispatch = useDispatch()
const changeNum = () => {
// dispatch({type: '字符串', val: 'val可以自定义,也可以不传'})
dispatch({type: 'ADD1'})
}
const changeNum2 = () => {
dispatch({type: 'ADD2', val: 2})
}
return (
<div>
<p>{num}</p>
<button onClick={changeNum}>num1</button>
<button onClick={changeNum2}>num2</button>
</div>
)
}
3. 对react-redux的优化
3.1 将state数据抽离
- 新建
index.js
用于存放数据和方法
// index.js 用于存放数据
// src/store/index.js 用于存放num模块数据
export default {
state: {
num: 0
},
actions: {
ADD1(newState, action) {
newState.num += 1
},
ADD2(newState, action) {
newState.num += action.val
}
},
// 名字统一管理
actionsName: {
add1: 'ADD1',
add2: 'ADD2'
}
}
reducer.js
// 仓库数据
import handleStore from './index'
const defaultState = {
// num: handleStore.state.num 多个属性这么写会很麻烦
...handleStore.state
}
// 当调用dispatch的时候会触发函数
let reducer = (state = defaultState, action) => {
// action:dispatch传递过来的对象
// 对数据进行深拷贝
let newState = JSON.parse(JSON.stringify(state))
switch (action.type) {
case handleStore.actionsName.add1:
// handleStore.actions.ADD1(newState, action)
// 将add1抽离
handleStore.actions[handleStore.actionsName.add1](newState, action)
break;
case handleStore.actionsName.add2:
handleStore.actions[handleStore.actionsName.add2](newState, action)
break;
default:
break;
}
return newState
}
export default reducer
页面的使用参考2.4
3.2 仓库模块化
仓库目录结构:
modules
:用于对模块进行划分- 不同模块目录:
index.js
:用于存放仓库数据和方法reducer.js
:创建不同模块的reducer
index.js
:redux仓库
拆分优化过程:
index.js
进行reducer合并
import { legacy_createStore, combineReducers, } from 'redux'
import num_reducer from './modules/num/reducer'
import arr_reducer from './modules/arr/reducer'
const reducers = combineReducers({
num_module: num_reducer,
arr_module: arr_reducer
})
const store = legacy_createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
export default store
页面上展示使用:
import { useSelector, useDispatch } from "react-redux"
export default () => {
const { num } = useSelector((state) => {
return {
// 需要加上模块名称使用
num: state.num_module.num
}
})
const dispatch = useDispatch()
const changeNum = () => {
dispatch({ type: 'ADD1' })
}
const changeNum2 = () => {
dispatch({ type: 'ADD2', val: 2 })
}
const { arr } = useSelector((state) => {
return {
// 需要加上模块名称使用
arr: state.arr_module.arr
}
})
const arrPush = () => {
dispatch({ type: 'ARRPUSH', val: 40 })
}
return (
<div>
<p>{num}</p>
<button onClick={changeNum}>num1</button>
<button onClick={changeNum2}>num2</button>
<hr />
<p>{arr}</p>
<button onClick={arrPush}>push</button>
</div>
)
}
- 对每个模块的
reducer.js
中的switch
语句进行优化
// 仓库数据
import handleStore from './index'
const defaultState = {
// num: handleStore.state.num 多个属性这么写会很麻烦
...handleStore.state
}
// 当调用dispatch的时候会触发函数
let reducer = (state = defaultState, action) => {
// action:dispatch传递过来的对象
let newState = JSON.parse(JSON.stringify(state))
// 优化前:
// switch (action.type) {
// case handleStore.actionsName.add1:
// // handleStore.actions.ADD1(newState, action)
// // 将add1抽离
// handleStore.actions[handleStore.actionsName.add1](newState, action)
// break;
// case handleStore.actionsName.add2:
// handleStore.actions[handleStore.actionsName.add2](newState, action)
// break;
// default:
// break;
// }
// 优化后
for (const key in handleStore.actionsName) {
if (action.type === handleStore.actionsName[key]) {
handleStore.actions[action.type](newState, action)
break
}
}
return newState
}
export default reducer
- 根据每个模块的
index.js
中的actions
的名字自动生成actionsName
,也可以将actionsName
进行提取到单独文件统管理
const store = {
state: {
num: 0
},
actions: {
ADD1(newState, action) {
newState.num += 1
},
ADD2(newState, action) {
newState.num += action.val
}
},
// 名字统一管理(最好将key=value=actions的方法名)
// actionsName: {
// ADD1: 'ADD1',
// ADD2: 'ADD2'
// }
}
let actionsName = {}
for (const key in store.actions) {
actionsName[key] = key
}
Object.assign(store, { actionsName })
export default store
效果及其如何使用:
- 效果:做到了只有修改每个模块的
index.js
中的数据和方法,不去动reducer.js
文件,使用起来跟``vuex`一样 - 复制
index.js
和reducer.js
,在index.js
的state
中添加数据,actions
添加方法即可;在store/index.js
中引入不同模块的reducer
4. 异步action
4.1 跟之前的一样,需要安装redux-thunk
4.2 在store/index.js
中配置
import { legacy_createStore, combineReducers, compose, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk'
import num_reducer from './modules/num/reducer'
import arr_reducer from './modules/arr/reducer'
const reducers = combineReducers({
num_module: num_reducer,
arr_module: arr_reducer
})
// 判断是否有__REDUX_DEVTOOLS_EXTENSION__COMPOSE__这个开发者工具模块
let composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION__COMPOSE__() : compose
const store = legacy_createStore(reducers, composeEnhancers(applyMiddleware(thunk)))
export default store
4.3 组件中使用
import { useSelector, useDispatch } from "react-redux"
export default () => {
const { num } = useSelector((state) => {
return {
num: state.num_module.num
}
})
const dispatch = useDispatch()
const asyncChangeNum = () => {
// 异步用法:dispatch中传入回调
dispatch((disp) => {
setTimeout(() => {
disp({ type: 'add1', val: 1 })
}, 1000)
})
}
return (
<div>
<p>{num}</p>
<button onClick={asyncChangeNum}>num异步+1</button>
</div>
)
}
4.4 优化:将组件内的方法抽取到每个模块的index.js
中
index.js
:
const store = {
state: {
num: 0
},
actions: {
add1(newState, action) {
newState.num += action.val
}
},
// 异步方法
asyncActions: {
asyncAdd1(dispathch) {
setTimeout(() => {
dispathch({ type: 'add1', val: 1 })
}, 1000)
}
}
}
let actionsName = {}
for (const key in store.actions) {
actionsName[key] = key
}
Object.assign(store, { actionsName })
export default store
组件内:
import { useSelector, useDispatch } from "react-redux"
export default () => {
const { num } = useSelector((state) => {
return {
num: state.num_module.num
}
})
const dispatch = useDispatch()
const asyncChangeNum = () => {
// 异步用法:dispatch中传入回调
dispatch(调用asyncAdd1方法即可)
}
return (
<div>
<p>{num}</p>
<button onClick={asyncChangeNum}>num异步+1</button>
</div>
)
}