React作為一個(gè)用于構(gòu)建用戶界面的JavaScript庫,很多人認(rèn)為React僅僅只是一個(gè)UI 庫,而不是一個(gè)前端框架,因?yàn)樗跀?shù)據(jù)管理上是缺失的。在做一個(gè)小項(xiàng)目的時(shí)候,維護(hù)的數(shù)據(jù)量不多,管理/維護(hù)數(shù)據(jù)用useState/useRef就足夠了;可是當(dāng)項(xiàng)目變大,需要的數(shù)據(jù)量成百上千,然后就會發(fā)現(xiàn):
-
全局變量到處都是。
-
在某些組件里定義的數(shù)據(jù)無法傳遞到其他組件里。
-
數(shù)據(jù)傳來傳去找不到定義位置,很難維護(hù)。
因此這時(shí)候就需要數(shù)據(jù)管理了。
最簡單的數(shù)據(jù)管理
就是把這些useState/useRef定義的數(shù)據(jù)放到根組件上,然后哪個(gè)子組件用,就用props傳下去,這樣沒有其他概念淺顯易懂,也起到了一定的數(shù)據(jù)管理的作用。但這樣做的缺點(diǎn)就是這些數(shù)據(jù)需要在子組件一層層的傳下去,代碼要寫很多,比較麻煩,如果不嫌麻煩的話,在大型項(xiàng)目里,這么做其實(shí)也沒什么問題了。
更進(jìn)一步的數(shù)據(jù)管理,用useContext
React的api,useContext,正是為了解決數(shù)據(jù)層層傳遞的問題而出現(xiàn)的,它可以看作是一個(gè)數(shù)據(jù)中心,所有需要管理的數(shù)據(jù)都在這里。
它怎么用呢,首先新開一個(gè)文件context.js,在里用React.createContext()定義一個(gè)Context然后導(dǎo)出:
//context.js
import React from "react";
export const Context = React.createContext();
然后在根節(jié)點(diǎn)這里,用這個(gè)Context的Provider屬性將整個(gè)根節(jié)點(diǎn)包裹?。?/p>
// rootView.jsx
import React from "react";
import { Context } from "./context";
export default function RootView() {
const defaultValue = {a: 1, b: 'hello'};
return <Context.Provider value={defaultValue}>
<View class='root-view'>
...各種子組件...
</View>
</Context.Provider>;
}
這里的defaultValue就是我們數(shù)據(jù)中心的所有數(shù)據(jù)的初始化的默認(rèn)值。
然后在子組件里,不管是子組件還是孫組件還是孫孫組件,都不用再把 props 當(dāng)傳家寶傳下去了,只需要在組件里像useState一樣調(diào)用useContext,就能獲取到數(shù)據(jù)中心的所有數(shù)據(jù):
//child.jsx
import React, { useContext } from "react";
import { Context } from "./context";
export default function() {
const state = useContext(Context);
return <Text>{state.b}</Text>
}
state就是數(shù)據(jù)中心的所有數(shù)據(jù),可以理解為useState中的State,這樣這個(gè)子組件顯示的就是上面默認(rèn)的初始化數(shù)據(jù)“hello”,但這還不夠好用,因?yàn)槟壳斑€沒有辦法改變數(shù)據(jù),那么我們接下來就需要對defaultValue做一些變動,把這些數(shù)據(jù)都用useState變成響應(yīng)式的,然后再一股腦地傳進(jìn)Provider的value里:
// rootView.jsx
import React from "react";
import { Context } from "./context";
export default function RootView() {
const [value, setValue] = useState({a: 1, b: 'hello'});
return <Context.Provider value={{value, setValue}}>
<View class='root-view'>
...各種子組件...
</View>
</Context.Provider>;
}
然后在子組件里這樣調(diào)用:
//child.jsx
import React, { useContext } from "react";
import { Context } from "./context";
export default function() {
const state = useContext(Context);
useEffect(()=>{
state.setValue({...state.value, b: 'world'});
}, []);
return <Text>{state.value.b}</Text>
}
然后,這個(gè)子組件顯示的就是已經(jīng)改動的數(shù)據(jù)“world”,關(guān)于Context還有一個(gè)比較重要的點(diǎn)是:當(dāng)Context Provider的value發(fā)生變化時(shí),他的所有調(diào)用useContext的子組件,都會重新渲染,這往往會造成比較嚴(yán)重的性能問題,在大型項(xiàng)目里百分百會出現(xiàn)。
第一個(gè)問題是state改變,造成Provider標(biāo)簽下的整體渲染。Context.Provider說到底還是組件,也是用React.createElement()實(shí)現(xiàn)的,也按照組件基本法來辦事,React.createElement()在每次props發(fā)生變動時(shí),都會創(chuàng)建一個(gè)新對象,那么只要讓props不發(fā)生變動就行了。我給Provider再包裹一層ProviderWrapper,然后在這個(gè)ProviderWrapper組件里去定義數(shù)據(jù),這樣,由于ProviderWrapper是不變的,那么在RootView組件里沒有任何狀態(tài)改變,子組件也用不著重復(fù)渲染了。
const ProviderWrapper = ({ children }) => {
const [value, setValue] =useState(defaultValue);
return (
<Context.Provider value={{ value, setValue }}>
{children}
</Context.Provider>
);
};
export default function RootView() {
return <ProviderWrapper>
<View class='root-view'>
...各種子組件...
</View>
</ProviderWrapper>;
}
這樣,babel在編譯的時(shí)候,標(biāo)簽轉(zhuǎn)譯成React.createElement()的時(shí)候,只是在RootView組件里完成轉(zhuǎn)譯,React.createElement()執(zhí)行完的節(jié)點(diǎn)數(shù)據(jù)將通過props.children傳入ProviderWrapper,在ProviderWrapper內(nèi)部就沒有重復(fù)的React.createElement(),這樣就避免了整體的重復(fù)渲染。
二是上述的所有調(diào)用useContext的子組件的局部重復(fù)渲染。即便在某一個(gè)子組件中只是使用了setState,并沒有使用state,但是當(dāng)state變動時(shí),這個(gè)子組件仍然會重復(fù)渲染,因?yàn)閮H僅是調(diào)用了useContext,但理論上來說是不需要重復(fù)渲染的。那解決辦法是什么呢?解決辦法就是將state和setState分別用不同的Provider傳入,這樣一個(gè)組件僅僅只是調(diào)用setState的話,就不會被state的變動影響而重復(fù)渲染:
const ProviderWrapper = ({ children }) => {
const [value, setValue] =useState(defaultValue);
return (
<SetValueContext.Provider value={{ setValue }}>
<ValueContext.Provider value={{ value }}>
{children}
</Context.Provider>
</Context.Provider>
);
};
其中SetValueContext和ValueContext是兩個(gè)毫不相干的有React.createContext()產(chǎn)生的對象,僅僅只是用來區(qū)分開state和setState,這樣在子組件里,如果只想調(diào)用setState,那么就通過React.useContext()引入SetValueContext即可,子組件就不會因state變動而重復(fù)渲染。
這樣基本上就差不多了,難懂的代碼多了一些,但冗余的代碼少了不少。概念越多就能解決的更多的問題,現(xiàn)在又出現(xiàn)了一個(gè)問題,state里有很多數(shù)據(jù),一些子組件引用了React.useContext(),但是對state里的一些數(shù)據(jù)是不關(guān)心用不到的,但這些數(shù)據(jù)在發(fā)生變動的時(shí)候,這些子組件也會重復(fù)渲染,說白了,就是state細(xì)粒度不夠的問題,但是本著盡可能消除重復(fù)渲染的思想,我們把state根據(jù)數(shù)據(jù)種類進(jìn)行拆分成多個(gè)state,這樣每個(gè)子組件調(diào)用對自己有用的state,這樣就減少了重復(fù)渲染:
const ProviderWrappers = ({ children }) => (
<LoginProviderWrapper>
<SignupProviderWrapper>
<MainPageProviderWrapper>
<MenuProviderWrapper>
{children}
</MenuProviderWrapper>
</MainPageProviderWrapper>
</SignupProviderWrapper>
</LoginProviderWrapper>
);
export default function RootView() {
return <ProviderWrappers>
<View class='root-view'>
...各種子組件...
</View>
</ProviderWrappers>;
}
等一下,代碼怎么變?nèi)哂嗔??我們最初的目的是什么?消除冗余,我們?yōu)榱讼环N冗余,帶來了另一種冗余,這是不可接受的,所以還得接著改,當(dāng)前情況是,由于state被拆分,造成出現(xiàn)了很多ProviderWrapper支持不同的state和setState,那么我們需要對這些ProviderWrapper進(jìn)行某種程度上的組合,至少我們可以用一個(gè)for循環(huán)去組合這些ProviderWrapper:
// RootView.tsx
function composeProviderWrappers(ProviderWrappers) {
const element;
for(ProviderWrapper of ProviderWrappers) {
element = <ProviderWrapper>{element}</ProviderWrapper>
}
return element;
}
export default function RootView() {
const ComposeProviderWrappers = composeProviderWrappers([LoginProviderWrapper, SignupProviderWrapper, MainPageProviderWrapper, MenuProviderWrapper]);
return <ComposeProviderWrappers>
<View class='root-view'>
...各種子組件...
</View>
</ComposeProviderWrappers>;
}
這個(gè)優(yōu)化意義不大,并沒有減少多少冗余代碼,但是說實(shí)話,我們現(xiàn)在已經(jīng)走歪了,而導(dǎo)致我們走歪的罪魁禍?zhǔn)?,就是React.useContext()的性能問題:只要調(diào)用React.useContext()的組件,當(dāng)state變動的時(shí)候,全部都會重新渲染?;氐阶铋_始說的,React相對于Framwork,其實(shí)它更類似于一個(gè)UI庫,用React本身的功能勉強(qiáng)實(shí)現(xiàn)數(shù)據(jù)管理,代價(jià)就是有很多坑,畢竟使用一些第三方數(shù)據(jù)管理庫例如Redux,zustand之類的,既能實(shí)現(xiàn)React.useContext()的功能,又能避免React.useContext()的問題,何樂而不為呢?下面就來介紹一些第三方數(shù)據(jù)管理庫:
Redux
Redux可以說是最正統(tǒng)的React數(shù)據(jù)管理工具,Redux的用法與React.useContext()類似,但沒有React.useContext()的缺點(diǎn),只有組件在使用到變動的數(shù)據(jù)的時(shí)候,這個(gè)組件才會重新渲染,如果你在因使用React.useContext()導(dǎo)致的無限渲染大卡關(guān)時(shí),不妨試試Redux。
Redux只有2KB,Redux Toolkit是官方推薦的編寫 Redux 邏輯的方法,使編寫 Redux 更加容易。安裝方式如下:
# NPM
npm install @reduxjs/toolkit redux
# Yarn
yarn add @reduxjs/toolkit redux
使用時(shí),首先像React.createContext()一樣,使用configureStore導(dǎo)出一個(gè)實(shí)例:
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: {}
})
然后用react-redux提供的Provider標(biāo)簽,將整個(gè)根節(jié)點(diǎn)包裹起來,唯一的區(qū)別就是,我們再也不用考慮擔(dān)心性能問題了,這里不會有的:
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './app/store'
import { Provider } from 'react-redux'
export default function RootView() {
return <Provider store={store}>
<View class='root-view'>
...各種子組件...
</View>
</Provider>;
}
然后不一樣的來了,創(chuàng)建slice:
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: state => {
// Redux Toolkit 允許我們在 reducers 寫 "可變" 邏輯。它
// 并不是真正的改變狀態(tài)值,因?yàn)樗褂昧?Immer 庫
// 可以檢測到“草稿狀態(tài)“ 的變化并且基于這些變化生產(chǎn)全新的
// 不可變的狀態(tài)
state.value += 1
},
decrement: state => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
// 每個(gè) case reducer 函數(shù)會生成對應(yīng)的 Action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
這里的createSlice實(shí)際上可以考慮為創(chuàng)建state和setState,reducers就是setState。然后將 Slice Reducers 添加到 Store 中:
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export default configureStore({
reducer: {
counter: counterReducer
}
})
最后就是使用了,在 React 組件中使用 Redux 狀態(tài)和操作:
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'
import styles from './Counter.module.css'
export function Counter() {
const count = useSelector(state => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
)
}
雖然起的名字不同,但是通過上述的React.useContext()的學(xué)習(xí),基本上也是能一一對應(yīng)的,最重要的是,這里不會再有性能問題了。
zustand
"Zustand" 只是德語的"state",一個(gè)輕量,現(xiàn)代的狀態(tài)管理庫,它的好處就是更簡單。
安裝:
npm install zustand
然后老生常談的定義一個(gè)實(shí)例:
const useStore = create(set => ({
votes: 0,
addVotes: () => set(state => ({ votes: state.votes + 1 })),
subtractVotes: () => set(state => ({ votes: state.votes - 1 })),
}));
然后,就可以使用了,這個(gè)真的比較方便:
function App() {
const addVotes = useStore(state => state.addVotes);
const subtractVotes = useStore(state => state.subtractVotes);
return <div className="App">
<h1>{getVotes} people have cast their votes</h1>
<button onClick={addVotes}>Cast a vote</button>
<button onClick={subtractVotes}>Delete a vote</button>
</div>
}
Rematch
Rematch在Redux的基礎(chǔ)上構(gòu)建并減少了樣板代碼和執(zhí)行了一些最佳實(shí)踐。Redux對于初學(xué)者來說簡直就是噩夢,他仿佛不是一個(gè)狀態(tài)管理工具,而是一個(gè)涉及了眾多概念的狀態(tài)管理模型。要想搞明白Redux如何使用,就要先了解10個(gè)以上名詞的含義;這還只是Redux的主流程使用中涉及到的名詞。Redux的主流程里充斥了各種各樣的概念,比如,Dispatch、Reducer、CreateStore、ApplyMiddleware、Compose、CombineReducers、Action、ActionCreator、Action Type、Action Payload、BindActionCreators...Rematch將這些概念進(jìn)行了整合,提出了一個(gè)更簡潔的狀態(tài)管理模型;
安裝:
npm install @rematch/core react-redux
首先,定義一個(gè)實(shí)例:
import { init } from "@rematch/core";
// 定義一個(gè)model,包含了之前redux中的一些內(nèi)容
// 擁有對應(yīng)的state和reducers
//model
const count = {
state: 0,
reducers: {
upBy: (state, payload) => state + payload,
},
};
// 使用init初始化
// 相當(dāng)于Redux中的store
init({
models: { count },
});
然后,就可以使用了:文章來源:http://www.zghlxwxcb.cn/news/detail-727347.html
import { connect } from "react-redux";
// Component
// 將count內(nèi)容賦值給count
const mapStateToProps = (state) => ({
count: state.count,
});
// 將指定動作傳輸給組件
const mapDispatchToProps = (dispatch) => ({
countUpBy: dispatch.count.upBy,
});
connect(mapStateToProps, mapDispatchToProps)(Component);
// connect倒是沒有怎么變
jotai,recoil,redux,rematch,zustand,Reducer,react數(shù)據(jù)管理的哲學(xué)文章來源地址http://www.zghlxwxcb.cn/news/detail-727347.html
到了這里,關(guān)于網(wǎng)絡(luò)建設(shè) 之 React數(shù)據(jù)管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!