1. 什么是Redux
redux 是一个独立专门用于做状态管理的 JS 库(不是 react 插件库)
2. 为什么使用Redux
-
因为对于react来说,同级组件之间的通信尤为麻烦,或者是非常麻烦了,所以我们把所有需要多个组件使用的state拿出来,整合到顶部容器,进行分发。
-
首先明确一点,Redux 是一个有用的架构,但不是非用不可。事实上,大多数情况,你可以不用它,只用 React 就够了
3. Redux的应用场景
从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux。
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
4. 面试题:Redux的三个核心概念
4.1 store
Redux的核心是store,它由Redux提供的 createStore(reducer) 这个方法生成
三个相关API:
store.getState():在组件中获取存储的store数据;
store.dispatch(action):分发action,并返回一个action,这是唯一能改变store中数据的方式;
subscribe(listener):注册(订阅)一个监听者,store发生变化的时候被调用。
4.2 reducer
reducer是一个纯函数,它根据previousState和action计算出新的state。指定了应用状态的变化如何响应action并发送到store的。
reducer(previousState,action)
4.3 action
action本质上是一个JavaScript对象,其中必须包含一个type字段来表示将要执行的动作,其他的字段都可以根据需求来自定义。数据的唯一来源,描述了有事情发生这一事实。
const ADD_TODO = 'ADD_TODO'
{
type: ADD_TODO,
value: '数据'
}
5. 面试题:Redux的工作流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eKex1aKM-1678248990019)(ipic\10022375-ca04027f2fedac55.webp)]
- 用户在UI组件中通过 store.dispatch触发action ;
- store 自动调用reducer,并传入之前的state,以及用户的action, 经过reducer返回新的state;
- 组件通过store.subscribe(listener) 订阅state的变化,可通过setState更新react UI。
6.案例:class类组件中Redux的使用步骤
- 需求分析
- 创建可以被所有组件共享的store数据city和color
- 在组件A中获取store中的city和color
- 在组件B中改变store中的city, 观察组件A中city和store中city同步
6.1 安装redux
npm i redux@4.1.2 -S
6.2 创建store
-
在src文件夹下创建store文件夹,在store下创建index.js
-
index.js代码
//1. 引入redux模块的createStore方法 import { createStore } from 'redux' //2. 配置store中state的初始值 let defaultValue = { city: '郑州', color: 'red' } //3. 用createStore创建store //createStore的参数必须是一个回调函数,该回调称为reducer const store = createStore((state = defaultValue, action) => { //4. 根据action的type类型对state进行处理,并返回最新的state //更改城市 if (action.type === "CHANGE_CITY"){ //!!!!!!不能直接更改state,直接更改,组件不会更新 let newState = JSON.parse(JSON.stringify(state)) //action.value为传来的新值 newState.city = action.value return newState } // 更改颜色 if (action.type === "CHANGE_COLOR"){ let newState = JSON.parse(JSON.stringify(state)) newState.color = action.value return newState } return state }) export default store
> reducer处理store的改变,第一个参数为传入的当前的state值,action为要执行的动作
> reducer是一个纯函数,它根据previousState和action计算出新的state。
>
> action是组件在改变state时传过来的数据,是一个对象,其中必须包含一个type字段来表示将要执行的动作
6.3 在class组件A中读取store数据
ChildA.js
import React, { Component } from 'react'
//1. 引入store
import store from './store'
export default class ChildA extends Component {
state = {
//2. 获取store数据
...store.getState()
}
componentDidMount(){
//3. 在组件创建时,订阅当store中数据改变时要执行的函数storeChange,
store.subscribe(this.storeChange)
}
//4. store中数据改变时要执行的函数
storeChange = ()=>{
this.setState(store.getState())
}
render() {
return (
<div>
<h1>ChildA</h1>
{/* 5. 使用store中的数据 */}
<p>城市:{this.state.city}</p>
<p>颜色:{this.state.color}</p>
</div>
)
}
}
6.4 在class组件B中更改store数据
ChildB.js
import React, { Component } from 'react'
// 1. 引入store
import store from './store'
export default class ChildB extends Component {
//2. 定义更改store数据的方法
changeCity = () => {
//3. 定义action
let action = {
type: "CHANGE_CITY",
value: '纽约'
}
//4. 用dispatch向store发送action
store.dispatch(action)
}
render() {
return (
<div>
{/* 5. 绑定事件,改变数据 */}
<button onClick={this.changeCity}>改变城市</button>
</div>
)
}
}
7. 结合react-redux在函数组件中使用store
7.1 安装 react-redux
npm i react-redux@8.0.5
7.2 在应用中注入store
App.js
import React from 'react'
import "./App.css"
//引入路由中的各种API
import {
BrowserRouter as Router,
} from 'react-router-dom'
import RenderRouter from './routes'
import TabBar from './TabBar'
//****************引入store
import store from './store'
//****************引入Provider组件
import {Provider} from 'react-redux'
export default function App() {
return (
<Provider store={store}>
<Router>
<RenderRouter></RenderRouter>
<TabBar></TabBar>
</Router>
</Provider>
)
}
7.3 在函数组件中使用store
import store from './store'
// useSelector Hook 用来获取store数据
// useDispatch Hook 用来引入dispatch方法,触发action,更改store
import { useSelector,useDispatch } from 'react-redux'
export default function Cart() {
const {city} = useSelector(state=>state)
const dispatch = useDispatch()
const change = ()=>{
let action = {
type: 'CHANGE_CITY',
value: '北京'
}
dispatch(action)
}
return (
<div>
city: {city}
<hr />
<button onClick={change}>改变city</button>
</div>
)
}
8. 扩展: 拆分reducer和actionType
8.1 为什么拆分reducer
如果一个项目,比较大,需要redux存储的状态数据比较多时,reducer.js无疑是会非常臃肿的。所以为了简化reducer.js文件,我们应该按照功能模块将这个大的reducer.js文件,拆分成若干个reducer.js。那么这里就需要使用redux里面的一个方法:combineReducers
8.2 为什么拆分actionType
首先当action.type不拆分的话在组件中的actionType要对应到reducer里的actionType,并且一模一样
当你操作时type如果差一个字符是不会执行Reducer的action的 且控制台不会报错。所以要把actionType拆分出来 当做变量引入时,当你输入错误,控制台会报错。这时查找原因会非常容易。
8.3 步骤
-
在store中创建actionType.js
export const CHANGE_CITY = 'changeCity' export const CHANGE_COLOR = 'changeColor'
-
在store中创建cityReducer.js
可单独管理与城市相关的数据和操作
import {CHANGE_CITY} from './actionType' const defaultValue = { city: '北京', } const cityReducer = (state=defaultValue,action)=>{ let {type,value} = action if (type === CHANGE_CITY){ const newState = JSON.parse(JSON.stringify(state)) newState.city = value return newState } return state } export default cityReducer
-
在store中创建colorReducer.js
import {CHANGE_COLOR} from './actionType' const defaultValue = { color: 'red' } const colorReducer = (state=defaultValue,action)=>{ let {type,value} = action if (type === CHANGE_COLOR){ const newState = JSON.parse(JSON.stringify(state)) newState.color = value return newState } return state } export default colorReducer
-
修改store/index.js中的代码
import { createStore,combineReducers } from 'redux' import cityReducer from './cityReducer' import colorReducer from './colorReducer' //使用combineReducers方法合并reducer const reducer = combineReducers({ a: cityReducer, b: colorReducer }) //createStore的回调函数称为reducer const store = createStore(reducer) export default store
如果访问cityReducer中数据,在组件中需要指定模块名 this.state.a.city, 其它使用方法都不变
9. actionCreator拆分action
作用: actionCreator: 统一管理action
store/actionCreator.js
import {CHANGE_CITY} from './actionType'
export const getCity = (value)=> (
{
type: CHANGE_CITY,
value
}
)
ChildB.js
const dispatch = useDispatch()
const changeCity = () => {
const action = getCity('北京')
dispatch(action)
}
10. redux-thunk中间件
redux的中间件,专门用来处理异步请求,使dispatch可以接受一个函数做为参数,从而从组件中把异步请求封装到actionCreator中
-
安装
npm i redux-thunk@2.4.1 -S
-
store/index.js
import { createStore,combineReducers,applyMiddleware } from 'redux' import thunk from "redux-thunk"; import cityReducer from './cityReducer' import colorReducer from './colorReducer' const reducer = combineReducers({ a: cityReducer, b: colorReducer }) const store = createStore(reducer,applyMiddleware(thunk)) export default store
-
不使用redux-thunk的异步请求
dispatch的action只能是一个对象,异步操作必须放在组件中
-
使用redux-thunk的异步请求
dispatch的action可以是一个有异步操作的函数,从而在actionCreator中集中管理异步请求
-
actionCreator.js
export const getList = (dispatch)=>{ setTimeout(() => { let list = ['上海', '开封', '安阳'] let action = { type: CHANGE_CITYLIST, value: list } dispatch(action) }, 2000) }
-
ChildA.js
import { getList } from './store/actionCreator' //省略其它代码 const dispatch = useDispatch() useEffect(() => { dispatch(getList) }, [dispatch]) let list = ['上海', '开封', '安阳'] let action = { type: CHANGE_CITYLIST, value: list } dispatch(action) }, 2000) }
-
ChildB.js
import { getList } from './store/actionCreator' //省略其它代码 const dispatch = useDispatch() useEffect(() => { dispatch(getList) }, [dispatch])
-