- 作者:老汪软件技巧
- 发表时间:2024-08-23 04:01
- 浏览量:
一.ReactContextApi
useContext 是 React 内置的状态管理解决方案,在一定程度上解决了组件状态共享的问题,看一个简单的 demo
import { FC, PropsWithChildren, createContext, useContext, useState } from "react";
const Demo1 = () => {
const { demo1, setDemo1 } = useContext(context);
console.log(' demo1 渲染了');
return <div>
demo1: {demo1}
<button onClick={() => setDemo1(demo1 + 1)}>加一button>
div>;
};
const Demo2 = () => {
const { demo2, setDemo2 } = useContext(context);
const [num, setNum] = useState(0);
console.log(" demo2 渲染了");
return <div>
demo2-Context: {demo2}
<button onClick={() => setDemo2(demo2 + 1)}>加一button>
<br />
demo2-state: {num}
<button onClick={() => setNum(num + 1)}>加一button>
div>;
};
//加入contextProvide但是没有使用useContext
const Demo3 = () => {
const [num, setNum] = useState(0);
return <div>
demo3-state: {num}
<button onClick={() => setNum(num + 1)}>加一button>
div>;
};
interface ContextType {
demo1: number;
demo2: number;
setDemo1: (demo1: number) => void;
setDemo2: (demo2: number) => void;
}
const context = createContext<ContextType>({
demo1: 0,
demo2: 0,
setDemo1: () => { },
setDemo2: () => { }
});
const Provider: FC<PropsWithChildren> = ({ children }) => {
const [demo1, setDemo1] = useState(0);
const [demo2, setDemo2] = useState(0);
return (
<context.Provider
value={{
demo1,
demo2,
setDemo1,
setDemo2
}}
>
{children}
context.Provider>
);
};
const App = () => (
<Provider>
< Demo1 />
< Demo2 />
<Demo3>Demo3>
Provider>
);
export default App;
测试如下:
useContext最大的问题就是:只要context里面的值发生了变化,它包裹的子组件里面,只要使用了useContext,它都要重新渲染。比如:demo3加入了provide里面,但是它却没有受到影响。
解决方案只有一个,比如:demo3从provide里面拿出来,demo1和demo2只能把这两个值拆分成2个context了。现在又出现了多层嵌套,而且还会出现嵌套太深的问题。
终极解决方案: redux、zustand、jotai 等状态管理库。
const App = () => (
<AaaProvider>
<BbbProvider>
<Aaa />
<Bbb />
BbbProvider>
AaaProvider>
);
二.redux
redux是我最讨厌的状态管理库,太烦了用起来真的和说日语一样拗口,东一榔头西一棒槌,用着用着就想骂人,头顶一万个草泥马飘过。redux和context之间最大的区别就是,context烦的是浏览器,有不是我。redux烦的是我,而不是浏览器。好玩吧!
不发牢骚了,我简单的说一下吧!
redux并不是专属于react的状态管理库,它不但能在react里面使用,还能在vue里面使用。如果你要在react里面使用redux需要用react-redux来搭桥才能使用。
redux实现思路如下:
就是有一个仓库中心,我们把所有的state都放在store里面,然后你在编写纯函数reducer,这这个纯函数的主要功能就是操作store里面的state。在使用的时候,我们用dispatch去派发action就好了。如果dispatch派发的action是异步操作,就需要用到 redux-thunk 了,他是redux的中间件。
redux相关的知识点:
react-redux用connect将react组件和redux连接起来,它将UI组件变成了容器组件,connect方法接受两个参数:mapStateToProps和mapDispatchToProps,就是将redux里面的state和dispatch注入到组件的props里面去,我们就像使用父组件传入的值一样,直接使用仓库里面的值。
// 容器组件
import { connect } from "react-redux";
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction,
} from "../../redux/count_action";
const mapStateToProps = (state) => ({ count: state });
const mapDispatchToProps = (dispatch) => ({
increment: (number) => {
dispatch(createIncrementAction(number));
},
incrementAsync: (number) => {
dispatch(createIncrementAsyncAction(number, 500));
},
decrement: (number) => {
dispatch(createDecrementAction(number));
},
});
const CountUI = (props) => {
return <>{props.count}>;
};
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
一般情况下redux都是绑定在最底层的main文件上的。
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from './redux/store'
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<App />
Provider>,
document.getElementById("root")
);
这样整个应用所有组件都能够把数据存储到一个数据中心里面去了。其实它最大的优势就是集中管理数据,但是最大的劣势也是集中管理。当一个应用足够大的时候,你所有的数据都放在最底层去管理,到了最后项目成型需要扩展就成了难事,而且很多时候会牵一发而动全身,又变成了最大的缺点。
Redux采用全局状态管理的方式,将应用的状态存储在一个全局的store中。虽然这样可以方便地共享状态和数据,但也增加了状态管理的复杂性。状态的修改必须通过dispatch一个action来触发,这样的限制可能导致状态的更新变得不够直观和灵活。
缺点:
1.样板代码太多
2.有一定的学习成本
3.项目越大,数据越多,复杂性越高,难以扩展和调试
4.不适合小型项目和经常变动的项目
其实上面这些都是redux越来越没有人用的主要原因。
三.Zustand
其实zustand和redux是有点相似的,他们都使用 store 模式设计,但是zustand是react专用的,它内部使用了react的hook来解决state变化页面刷新的问题。
具体的原理参见:手写zustand
看看他的基本使用:
import { create } from 'zustand';
const useStore = create((set, get) => ({
a: '',
b: 0,
updateA: (value) => set(() => ({ a: value })),
updateB: (value) => set(() => ({ b: value })),
getA: () => get().a,
getB: () => get().b,
}));
export default function App() {
const {
a,
b,
updateA,
updateB,
getA,
getB } = useStore();
return (
<div style={{ margin: '100px' }}>
<input
onChange={(e) => updateA(e.currentTarget.value)}
value={a}
/>
<p>hello, {a}p>
<p>a 的值, {getA()}p>
<button onClick={() => { updateB(b + 1); }}>点我button>
<div>{b}div>
<p>b 的值, {getB()}p>
div>
);
}
就是利用create方法创建一个数据仓库,它把state,还有修改state的所有的方法都放进了这个仓库里面,进行集中管理,最后它返回出来一个hook。store定义以后,不管在哪里使用useStore,他都能操作数据,而且不会引发context的副作用,也不会像redux一样繁琐。它是我的真爱!
既然是真爱,我就不想说它的缺点了,他们列出来的缺点我也不赞同,哈哈哈!
zustand这么可爱,你怎么能说他的坏话呢?
不过在ts里面它的中间件写法会有点不一样
四.jotai
jotai 是原子状态管理库的代表人物,看原子状态你有没有想起tailwind,他是原子化css的框架。就是把每个样式做一个类,然后丢给你用:
class="text-base p-1 border border-black border-solid">
jotai也是这个意思
就是用atom去定义值和方法,jotai收到值和方法以后,它会在内部创建一个容器,把他们都收集起来,我内部有多少值,你不用管,你只需要关注你定义的方法和值就好了。
jotai和zustand最大的区别就在这里,数据中心是我自己创建的,还是状态库那边创建的。
异步请求:
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
async function getListById(id) {
const data = {
1: ['a1', 'a2', 'a3'],
2: ['b1', 'b2', 'b3', 'b4']
}
return new Promise((resolve) => {
setTimeout(() => {
resolve(data[id]);
}, 2000);
});
}
const listAtom = atom([]);
const dataAtom = atom((get) => {
return get(listAtom);
}, async (get, set, param) => {
const data = await getListById(param);
set(listAtom, data);
});
export default function App() {
const [list, fetchListData] = useAtom(dataAtom);
return <div>
<button onClick={() => fetchListData(2)}>列表222button>
<ul>
{
list.map(item => {
return <li key={item}>{item}li>
})
}
ul>
div>
}
多组件共用一个数据
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
async function getListById(id) {
const data = {
1: ['a1', 'a2', 'a3'],
2: ['b1', 'b2', 'b3', 'b4']
}
return new Promise((resolve) => {
setTimeout(() => {
resolve(data[id]);
}, 2000);
});
}
const listAtom = atom([]);
const dataAtom = atom((get) => {
return get(listAtom);
}, async (get, set, param) => {
const data = await getListById(param);
set(listAtom, data);
});
export default function App() {
const [list, fetchListData] = useAtom(dataAtom);
return <div>
<button onClick={() => fetchListData(2)}>列表222button>
<ul>
{
list.map(item => {
return <li key={item}>{item}li>
})
}
ul>
div>
}
其实和zustand差不多
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useStore = create(persist((set) => ({
count: 0,
setCount: (value) => set({ count: value})
}), {
name: 'count-key'
}))
export default function App() {
const count = useStore(state => state.count);
const setCount = useStore((state) => state.setCount);
return <div>
count: {count}
<button onClick={() => setCount(count + 1)}>加一button>
div>
}
对比zustand和jotai
zustand 是所有 state 放在全局 store 里,然后用到的时候 selector 取需要的部分。
jotai 是每个 state 单独声明原子状态,用到的时候单独用或者组合用。
一个自上而下,一个自下而上,这是两种思路。
不管是状态、派生状态、异步修改状态、中间件等方面,zustand 和 jotai 都是一样的。
区别只是一个是全局 store 里存储所有 state,一个是声明原子 state,然后组合。
这只是两种思路,没有好坏之分,看你业务需求,适合哪个就用那个,或者你习惯哪种思路就用哪个。