React 状态管理:redux recoll jotal mobx zustand
React State
React 为我们提供了创建状态与更新状态的Api -- useState
const [count, setCount] = useState(0)
//count:变量 变更可驱动视图更新实现组件的渲染 setCount更新count数据
setCount(count+1) 等同于 setCount(c => c+1 )
组件中使用: {count}
就近取值:
class function state值修改 两种不同的写法
import React ,{useState} from "react";
class App extends React.Component {
constructor (props) {
super(props);
// const [count, setCount] = useState(0)
this.state = { count:0 } }
render () {
return (
<div>
<p>Count:{this.state.count}</p>
<button onClick={()=>this.setState({count:this.state.count+1})}>点击</button>
</div>
)
}}
import React, { useState } from 'react';
function App() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}> Click me </button>
</div> );}
export default App;
useReducer
import React, { useReducer } from 'react';function App() { // Declare a new state variable, which we'll call "count" function reducer(state,action){ switch(action.type){ case 'incremented_age': { return { ...state, age: state.age+2 } } case "hobby_change":{ return { ...state, hobby:action.payload } } } } const [state, dispatch] = useReducer(reducer,{age:23,hobby:'象棋'}) return ( <div> <p>You clicked {state.age} times</p> <button onClick={() => dispatch({type:'incremented_age'})}> Click me </button> <p> {state.hobby} </p> <button onClick={() => dispatch({type:'hobby_change',payload:'跳棋'})}> Click me </button> </div> );}export default App;
useContext
方式1
// pageContext page
import React from 'react'
export const PageContext = React.createContext({ //创建句柄
title:"Page title"
})
import { useContext } from "react";
import { PageContext } from "./PageContext";
export const PageTitle = () => {
const { title } = useContext(PageContext); //获取句柄传递的数据
return (
<div>
{/* <PageContext.Consumer>{({ title }) => title}</PageContext.Consumer> */}
{title}
</div>
);
};
父 数据提供者
<PageContext.Provider value = {{title:"Page title"}}>
<PageTitle />
<PageContext.Provider />
后代使用者
<PageContext.Consumer>
{({title}) =>{title}}
<PageContext.Consumer/>
面试官会问:Context 有什么缺点,怎么改进?
Context 因为是整个大对象,只要数据变更,以下所有内容有可能会需要 rerender。
-
细化 context value,拆分,PageContext、WorkSpaceContext
-
通过定义 xxxProvider,将数据更新局限在 children 层,不再是 PageContext.Provider,而是 PageProvider
export const FormProvider = < TFieldValues extends FieldValues, TContext = any, TTransformedValues extends FieldValues | undefined = undefined,
( props: FormProviderProps<TFieldValues, TContext, TTransformedValues>, ) => { const { children, ...data } = props; return ( <HookFormContext.Provider value={data as unknown as UseFormReturn}> {children} </HookFormContext.Provider> ); }; 原来的写法会影响到同级节点,同级节点一个变化,父级组件会rerender ,所以现在套了个壳子, 不存在同级节点,所以影响rerender
Redux
- 单一数据流
- state只读
- 使用纯函数执行状态修改
单向数据流:reducer ->产出state ->更新操作 view视图 ->触发actions -> 影响 Dispatcher ->修改Reducer 中state -> 影响view层
any : 允许赋值为任意类型,可访问任意属性和方法
void : 表示没有任何类型返回
redux 源码解析
源码部分: createStore 包含 getState dispatch subscribe 方法
function createStore(reducer,enhancer) { if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.'); } return enhancer(createStore)(reducer); } let state; const nextListeners = []; const getState = ()=>{ return state; } const dispatch = (action)=>{ state = reducer(state,action); for(const listener of nextListeners){ listener() } } const subscribe = (listener)=>{ nextListeners.push(listener); //订阅函数的返回值一般都是Unsubscribe return ()=>{ const index = nextListeners.indexOf(listener); nextListeners.splice(index,1); } } return { subscribe, getState, dispatch } } export default createStore
combineReducer 和 applyMiddleware
这两个方法用于增强 redux 能力,比如我们一个状态仓库可能存储了多个模块的状态,这些模块的 reducer 相对分散,这时我们想有一个方法合并这些分散 reducer,实现比较简单,运用函数柯里化,定义一个 stateMap,然后依次调用对应 reducer 并将得到的值写入 stateMap 中,完成更新。
combineReducer部分 :将合并的reducer 拆解成一个一个的然后在合并
export default function combineredux(reducers) { // 获取到所有 reducer key const reducerKeys = Object.keys(reducers); // 返回新的 reducer 函数 return function combination( state={}, action ) { // 定义新的 state,该 state 用来存储合并之后的所有模块 state const nextState = {}; // 遍历所有 reducer for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i]; const reducer = reducers[key]; // 先获取到之前 state 的值,这个是 createStore 中存储的值,该值是一个 state tree,包括了所有模块 state const previousStateForKey = state[key]; // 执行 reducer,出入的 state 参数正式上次存储的 state,调用后会获得计算后新的 state const nextStateForKey = reducer(previousStateForKey, action); // 将对应模块 state 挂到 nextState 对象上,且以 key 为键 nextState[key] = nextStateForKey; } /** * * 返回合并后的 state * */ return nextState; }; }
利用函数柯里化实现函数的中间件
import React ,{useSyncExternalStore}from 'react';import createStore from './redux/redux';import combineredux from './redux/combineredux';const personReducer = (state = { name: "heyi", age: 0 }, action) => { switch (action.type) { case "incremented_age": { return { ...state, age: state.age + 1, }; } default: return state } };const PageReducer = (state = { title: "home" }, action) => { switch (action.type) { case "change_title": { return { ...state, title: action.payload, }; } default: return state }};// 函数柯里化function compose(...funcs) { return funcs.reduce((a, b) => (...args) => a(b(...args)));}function applyMiddleware(...middlewares) { return (createStore) => ( reducer ) => { const store = createStore(reducer); let dispatch; const middlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args), }; const chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch, }; };}const store =createStore( combineredux({ person: personReducer, page: PageReducer }) );store.dispatch({type:"incremented_age"})store.dispatch({type:"change_title",payload:'haha'})console.log(store.getState())function App() { //当下外部状态和react 状态对接,快照处理const state = useSyncExternalStore(store.subscribe,store.getState) return ( <div> {/* <button onClick={() => store.dispatch({ type: "incremented_age" })}> Click me{state.person.age} </button> */} <button onClick={() => store.dispatch({ type: "change_title" ,payload:'haley'})}> Click me{state.page.title} </button> </div> );}export default App;
react 状态管理库
redux mobx recoil zustand valtio jotal hox