前端的狀態(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)管理機(jī)制。和大多數(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 (視圖)去操作(如點(diǎn)擊按鈕等),也就是 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
的計(jì)算過程就是 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í)大多數(shù)程序員使用 Redux 的最多的場景無非是從 A 頁面返回 B 頁面 需要保存 B 頁面的狀態(tài)。
倘若項(xiàng)目不大,用 Redux
或 Vuex 是不是會顯得有些大?我們知道在 Vue
中有提供 keep-alive
讓我們緩存當(dāng)前組件,這樣就可以解決上述的場景。
但是很遺憾在 React
中并沒有像 Vue 一樣的 keep-alive
。社區(qū)中的方案普遍是改造路由,但是這種改造對于項(xiàng)目入侵過大且不易維護(hù),另外在 react-router v5
中也取消了路由鉤子。于是,對小型項(xiàng)目來說自己封裝一個函數(shù)也不失為良策。(當(dāng)然你想用 Redux
也沒問題,咱們只是探索更多方式)
還是用圖書館來舉例子,現(xiàn)在有一個圖書館管理系統(tǒng),你從列表頁(list)跳入詳情頁(detail
)需要保存列表頁的狀態(tài)(如搜索欄的狀態(tài)等)。
假設(shè)你使用的技術(shù)棧是(react + antd
),來手寫一個簡單粗暴的(核心是利用context
來進(jìn)行跨組件數(shù)據(jù)傳遞):
// KeepAlive.js export default function keepAliveWrapper() { return function keepAlive(WrappedComponent) { return class KeepAlive extends WrappedComponent { // ps constructor(props) { super(props) // do something ... } componentDidMount() { const { keepAlive: { fieldsValue }, } = this.context // do something ... super.componentDidMount() } render() { // do something ... return super.render() } } } }
這里提一下為什么要繼承原組件(// ps)
如果常規(guī)寫法返回一個類組件(class KeepAlive extends React.Component
),那本質(zhì)上就是父子組件嵌套,父子組件的生命周期都會按秩序執(zhí)行,所以每當(dāng)回到列表頁獲取狀態(tài)時,會重復(fù)渲染兩次,這是因?yàn)?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)
你就立馬知道這是一個有緩存的組件(方便閱讀及維護(hù))。
// 在頁面使用時 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)文章
JS數(shù)組方法some、every和find的使用詳情
這篇文章 要給大家介紹的是JS數(shù)組方法some、every和find的使用的一些相關(guān)資料,感興趣的小伙伴一起來學(xué)習(xí)吧2021-09-09JS輕量級函數(shù)式編程實(shí)現(xiàn)XDM一
這篇文章主要為大家介紹了JS輕量級函數(shù)式編程實(shí)現(xiàn)XDM示例詳解第1/3篇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06微信小程序 swiper組件構(gòu)建輪播圖的實(shí)例
這篇文章主要介紹了微信小程序 swiper組件構(gòu)建輪播圖的實(shí)例的相關(guān)資料,如有疑問請留言或者到本站社區(qū)交流討論,需要的朋友可以參考下2017-09-09