前端的狀態(tài)管理(下)
前言:
續(xù)上篇前端的狀態(tài)管理(上),沒想到很多讀者朋友們這么關(guān)注,感謝大家的支持和建議,我只是發(fā)表個人看法以及自己的一些思考也許不夠全面,使用 Vue 舉例也僅僅只是作為引路且 Vue 的關(guān)注度也是較高的。那有些朋友想聽聽除 Vuex 的其他方案,今天將從 Redux 入手逐漸拓展(如標(biāo)題一樣淺談)。
1、Redux
作為 React 全家桶的一員,Redux 試圖為 React 應(yīng)用提供可預(yù)測化的狀態(tài)管理機制。和大多數(shù)狀態(tài)管理方案一樣,Redux 的思想也是發(fā)布訂閱模式,我們還是以圖書館為例來簡單了解一下 Redux。
Redux 的基礎(chǔ)操作大致為:
Store(圖書館管理員)State(書本)Action(借書單)store.dispatch(提交借書單)Reducer(包裝書本)store.subscribe(接收書本)
1.1、Store(圖書館管理員)
Store 可以看作是一個容器,整個應(yīng)用只有一個 Store。就好比你想要借書只能找圖書管理員。
import { createStore } from 'redux'
const store = createStore(reducer);
1.2、State(書本)
對于 State 來說他只能通過 Action 來改變(既你借書只能提交借書單來借),不應(yīng)該直接修改 State 里的值。
使用store.getState()可以得到state。
import { createStore } from 'redux'
const store = createStore(reducer)
store.getState()
1.3、Action(借書單)
你想借書咋辦?那當(dāng)然是向管理員提交借書單了。那用戶是接觸不到 State 的,只能通過 View (視圖)去操作(如點擊按鈕等),也就是 State 的變化對應(yīng) View 的變化,就需要 View 提交一個 Action 來通知 State 變化。(既通過提交借書單給管理員才會有接下來一系列的其他操作)
Action 是一個自定義對象,其中type屬性是約定好將要執(zhí)行的操作。
const action = {
type: 'click',
info: '提交借書單'
}
1.4、store.dispatch (提交借書單)
store.dispatch 是 View 發(fā)出 Action 的唯一方法,他接受一個 Action 對象(既提交借書單),只是把單的信息給了圖書管理員,他在根據(jù)單子來搜索相應(yīng)的書本。
store.dispatch(action)
1.5、Reducer(包裝書本)
Store 收到一個 Action 后,必須給出一個新的 State ,這樣 View 才會發(fā)生變化,而新的 State 的計算過程就是 Reducer 來完成的。(既拿到單子將你的書本打包裝袋等)
Reducer 是一個自定義函數(shù),它接受 Action 和當(dāng)前的 State 作為參數(shù),返回一個新的 State。
const reducer = (state, action) => {
return `action.info: ${state}` // => 提交借書單:紅樓夢
}
store.subscribe(接收書本)
當(dāng) State 一旦發(fā)生變化,那么 store.subscribe() 就會監(jiān)聽到自動執(zhí)行更新 View。
const unsubscribe = store.subscribe(() => {
render() {
// 更新view
}
})
// 也可以取消訂閱(監(jiān)聽)
unsubscribe()
小結(jié):

相信剛接觸 Redux 的同學(xué)都會覺得 Redux 比較繁瑣,這也與他的思想有關(guān):Redux 里的一切應(yīng)該都是確定的。
盡管在 Redux 里還是沒辦法做到一切都是確定的(如異步)但是應(yīng)該保證大多數(shù)部分都是確定的包括:
- 視圖的渲染是可確定的
- 狀態(tài)的重建是可確定的
至于為什么要這么做,上一篇我已有提及。他的重要之處在于:便于應(yīng)用的測試,錯誤診斷和 Bug 修復(fù)。
2、狀態(tài)管理的目的
那其實大多數(shù)程序員使用 Redux 的最多的場景無非是從 A 頁面返回 B 頁面 需要保存 B 頁面的狀態(tài)。
倘若項目不大,用 Redux 或 Vuex 是不是會顯得有些大?我們知道在 Vue 中有提供 keep-alive 讓我們緩存當(dāng)前組件,這樣就可以解決上述的場景。
但是很遺憾在 React 中并沒有像 Vue 一樣的 keep-alive。社區(qū)中的方案普遍是改造路由,但是這種改造對于項目入侵過大且不易維護,另外在 react-router v5 中也取消了路由鉤子。于是,對小型項目來說自己封裝一個函數(shù)也不失為良策。(當(dāng)然你想用 Redux 也沒問題,咱們只是探索更多方式)
還是用圖書館來舉例子,現(xiàn)在有一個圖書館管理系統(tǒng),你從列表頁(list)跳入詳情頁(detail)需要保存列表頁的狀態(tài)(如搜索欄的狀態(tài)等)。
假設(shè)你使用的技術(shù)棧是(react + antd),來手寫一個簡單粗暴的(核心是利用context來進(jìn)行跨組件數(shù)據(jù)傳遞):
這里提一下為什么要繼承原組件(// ps)
如果常規(guī)寫法返回一個類組件(class KeepAlive extends React.Component),那本質(zhì)上就是父子組件嵌套,父子組件的生命周期都會按秩序執(zhí)行,所以每當(dāng)回到列表頁獲取狀態(tài)時,會重復(fù)渲染兩次,這是因為 HOC 返回的父組件調(diào)用了原組件的方法,到導(dǎo)致列表頁請求兩次,渲染兩次。
若使 HOC(高階組件)繼承自原組件,就不會生產(chǎn)兩個生命周期交替執(zhí)行,很好的解決這個問題。
// main.jsx 根組件
import React from 'react'
const appContext = React.createContext()
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
keepAlive: {}, // 緩存對象
isCache: false, // 是否緩存
fieldsValue: {} // 緩存表單值
}
}
componentDidMount() {
// 初始化
const keepAlive = {
isCache: this.state.isCache,
toggle: this.toggleCache.bind(this),
fieldsValue: this.state.fieldsValue,
}
this.setState({ keepAlive })
}
// 這里封裝一個清除狀態(tài)的方法 防止渲染警告(you can't set fields before render ...)
// 比如 list1 => list1/detail => list2 需要將跳轉(zhuǎn)放在以下回調(diào)中并清除狀態(tài)
toggleCache(isCache = false, payload, callback) {
const { fieldsValue = null } = payload
const keepAlive = {
isCache,
fieldsValue,
toggle: this.toggleCache.bind(this),
}
const fn = typeof callback === 'function' ? callback() : void 0
this.setState(
{
keepAlive,
},
() => {
fn
}
)
}
render() {
const { keepAlive } = this.state
<appContext.Provider value={{ keepAlive }}>
// your routes...
</appContext.Provider>
}
}
至于為什么不直接使用 context,而多封裝一層 keepAlive,是為了統(tǒng)一處理 context,在組件頭部中使用裝飾器這種簡潔的寫法(@keepAlive)你就立馬知道這是一個有緩存的組件(方便閱讀及維護)。
// 在頁面使用時
import React from 'react'
import keepAlive from '../keepAlive'
// keepAlive的位置需要放在原組件最近的地方
@keepAlive()
class App extends React.Component {
constructor(props){
super(props)
this.state = {
// init something...
}
}
componentDidMount() {
// do something...
if(this.context.keepAlive.fieldsValue) {
const { tableList } = this.context.keepAlive.fieldsValue
console.log('緩存啦:',tableList) // 緩存啦:['1', '2']
}
}
// 查看詳情
detail = () => {
this.context.keepAlive.fieldsValue = {
tableList: ['1', '2']
}
// jump...
}
// 當(dāng)需要跨一級路由進(jìn)行跳轉(zhuǎn)時,如 list1 => list1/detail(下面這個方法應(yīng)該在詳情頁里) => list2,此時需要處理一下警告
toList2 = () => {
this.context.keepAlive.toggle(false, {}, () => {
// jump...
})
}
}
在上述使用了裝飾器寫法,簡單說一下,需要先配置以下 babel 放可使用哦~
npm install -D @babel/plugin-proposal-decorators
在jsconfig.json中(無則新建)配置一下:
{
"compilerOptions": {
"experimentalDecorators": true
},
"exclude": [
"node_modules",
"dist"
]
}
在 .babelrc 配置:
{
"plugins": [
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
}
上面方法比較適用剛才說的場景(從 A 頁面返回 B 頁面 需要保存 B 頁面的狀態(tài)),有人的說,你這樣還不如用 Redux 或 Mobx 不就好了?跨路由跳轉(zhuǎn)還得手動清除狀態(tài)防止警告。。。仁者見仁,智者見智吧。自己封裝了也說明自己有所研究,不論他易或難,編程本身不就該是不斷探索嗎,哈哈。盡管你寫的可能不夠好或是咋樣,虛心接受批評就是了,畢竟厲害的人多著呢。
總結(jié):
到此這篇關(guān)于前端的狀態(tài)管理的文章就介紹到這了,更多相關(guān)前端的狀態(tài)管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
回顧上篇:淺談前端的狀態(tài)管理(上)
相關(guān)文章
前端JavaScript算法找出只出現(xiàn)一次的數(shù)字
這篇文章主要為大家介紹了前端JavaScript算法找出只出現(xiàn)一次的數(shù)字的算法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07

