React手寫redux過程分步講解
提起 Redux 我們想到最多的應該就是 React-redux 這個庫,可是實際上 Redux 和 React-redux 并不是同一個東西, Redux 是一種架構模式,源于 Flux。 React-redux 是 Redux 思想與 React 結合的一種具體實現(xiàn)。
在我們使用 React 的時候,常常會遇到組件深層次嵌套且需要值傳遞的情況,如果使用 props 進行值的傳遞,顯然是非常痛苦的。為了解決這個問題,React 為我們提供了原生的 context API,但我們用的最多的解決方案卻是使用 React-redux 這個基于 context API 封裝的庫。
本文并不介紹 React-redux 的具體用法,而是通過一個小例子,來了解下什么是 redux。
好了,現(xiàn)在我們言歸正傳,來實現(xiàn)我們自己的 redux。
一、最初
首先,我們用 creat-react-app 來創(chuàng)建一個項目,刪除 src 下冗余部分,只保留 index.js,并修改 index.html 的 DOM 結構:
# index.html <div id="root"> <div id="head"></div> <div id="body"></div> </div>
我們在 index.js 中創(chuàng)建一個對象,用它來儲存、管理我們整個應用的數(shù)據狀態(tài),并用渲染函數(shù)把數(shù)據渲染在頁面:
const appState = {
head: {
text: '我是頭部',
color: 'red'
},
body: {
text: '我是body',
color: 'green'
}
}
function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.head.text;
head.style.color = state.head.color;
}
function renderBody (state){
const body = document.getElementById('body')
body.innerText = state.body.text;
body.style.color = state.body.color;
}
function renderApp (state){
renderHead(state);
renderBody(state);
}
renderApp(appState);此時運行代碼,打開頁面,我們可以看到,在 head 中已經出現(xiàn)了紅色字體的‘我是頭部’,在 body 中出現(xiàn)了綠色字體的‘我是body’。
如果我們把 head 和 body 看作是 root 中的兩個組件,那么我們已經實現(xiàn)了一個全局唯一的 state 。這個 state 是全局共享的,隨處可調用的。
我們可以修改 head 的渲染函數(shù),來看下效果:
function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.head.text + '--' + state.body.text;
head.style.color = state.head.color;
state.body.text = '我是經過 head 修改后的 body';
}我們看到,在 head 渲染函數(shù)中,我們不僅可以取用 body 屬性的值,還可以改變他的值。這樣就存在一個嚴重的問題,因為 state 是全局共用的,一旦在一個地方改變了 state 的值,那么,所有用到這個值的組件都將受到影響,而且這個改變是不可預期的,顯然給我們的代碼調試增加了難度系數(shù),這樣的結果是我們不愿意看到的!
二、dispatch
現(xiàn)在看來,在我們面前出現(xiàn)了一個矛盾:我們需要數(shù)據共享,但共享數(shù)據被任意的修改又會造成不可預期的問題!
為了解決這個矛盾,我們需要一個管家,專門來管理共享數(shù)據的狀態(tài),任何對共享數(shù)據的操作都要通過他來完成,這樣,就避免了隨意修改共享數(shù)據帶來的不可預期的危害!
我們重新定義一個函數(shù),用這個函數(shù)充當我們的管家,來對我們的共享數(shù)據進行管理:
function dispatch(state, action) {
switch (action.type) {
case 'HEAD_COLOR':
state.head.color = action.color
break
case 'BODY_TEXT':
state.body.text = action.text
break
default:
break
}
}我們來重新修改head 的渲染函數(shù):
function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.head.text + '--' + state.body.text;
head.style.color = state.head.color;
dispatch(state, { type: 'BODY_TEXT', text: '我是 head 經過調用 dispatch 修改后的 body' })
}dispatch 函數(shù)接收兩個參數(shù),一個是需要修改的 state ,另一個是修改的值。這時,雖然我們依舊修改了 state ,但是通過 dispatch 函數(shù),我們使這種改變變得可控,因為任何改變 state 的行為,我們都可以在 dispatch 中找到改變的源頭。
這樣,我們似乎已經解決了之前的矛盾,我們創(chuàng)建了一個全局的共享數(shù)據,而且嚴格的把控了任何改變這個數(shù)據的行為。
然而,在一個文件中,我們既要保存 state, 還要維護管家函數(shù) dispatch,隨著應用的越來越復雜,這個文件勢必會變得冗長繁雜,難以維護。
現(xiàn)在,我們把 state 和 dispatch 單獨抽離出來:
- 用一個文件單獨保存 state
- 用另一個文件單獨保存 dispatch 中修改 state 的對照關系 changeState
- 最后再用一個文件,把他們結合起來,生成全局唯一的 store
這樣,不僅使單個文件變得更加精簡,而且在其他的應用中,我們也可以很方便的復用我們這套方法,只需要傳入不同應用的 state 和修改 state 的對應邏輯 stateChange,就可以放心的通過調用 dispatch 方法,對數(shù)據進行各種操作了:參考前端手寫面試題詳細解答
# 改變我們的目錄結構,新增 redux 文件夾
+ src
++ redux
--- state.js // 儲存應用數(shù)據狀態(tài)
--- storeChange.js // 維護一套修改 store 的邏輯,只負責計算,返回新的 store
--- createStore.js // 結合 state 和 stateChange , 創(chuàng)建 store ,方便任何應用引用
--index.js
## 修改后的各個文件
# state.js -- 全局狀態(tài)
export const state = {
head: {
text: '我是頭部',
color: 'red'
},
body: {
text: '我是body',
color: 'green'
}
}
# storeChange.js -- 只負責計算,修改 store
export const storeChange = (store, action) => {
switch (action.type) {
case 'HEAD_COLOR':
store.head.color = action.color
break
case 'BODY_TEXT':
store.body.text = action.text
break
default:
break
}
}
# createStore.js -- 創(chuàng)建全局 store
export const createStore = (state, storeChange) => {
const store = state || {};
const dispatch = (action) => storeChange(store, action);
return { store, dispatch }
}
# index.js
import { state } from './redux/state.js';
import { storeChange } from './redux/storeChange.js';
import { createStore } from './redux/createStore.js';
const { store, dispatch } = createStore(state, storeChange)
function renderHead (state){
const head = document.getElementById('head')
head.innerText = state.text;
head.style.color = state.color;
}
function renderBody (state){
const body = document.getElementById('body')
body.innerText = state.text;
body.style.color = state.color;
}
function renderApp (store){
renderHead(store.head);
renderBody(store.body);
}
// 首次渲染
renderApp(store);通過以上的文件拆分,我們看到,不僅使單個文件更加精簡,文件的職能也更加明確:
- 在 state 中,我們只保存我們的共享數(shù)據
- 在 storeChange 中,我們來維護改變 store 的對應邏輯,計算出新的 store
- 在 createStore 中,我們創(chuàng)建 store
- 在 index.js 中,我們只需要關心相應的業(yè)務邏輯
三、subscribe
一切似乎都那么美好,可是當我們在首次渲染后調用 dispatch 修改
store 時,我們發(fā)現(xiàn),雖然數(shù)據被改變了,可是頁面并沒有刷新,只有在 dispatch 改變數(shù)據后,重新調用 renderApp() 才能實現(xiàn)頁面的刷新。
// 首次渲染
renderApp(store);
dispatch({ type: 'BODY_TEXT', text: '我是調用 dispatch 修改的 body' }) // 修改數(shù)據后,頁面并沒有自動刷新
renderApp(store); // 重新調用 renderApp 頁面刷新這樣,顯然并不能達到我們的預期,我們并不想在每次改變數(shù)據后手動的刷新頁面,如果能在改變數(shù)據后,自動進行頁面的刷新,當然再好不過了!
如果直接把 renderApp 寫在 dispatch 里,顯然是不太合適的,這樣我們的 createStore 就失去了通用性。
我們可以在 createStore 中新增一個收集數(shù)組,把 dispatch 調用后需要執(zhí)行的方法統(tǒng)一收集起來,然后再循環(huán)執(zhí)行,這樣,就保證了 createStore 的通用性:
# createStore
export const createStore = (state, storeChange) => {
const listeners = [];
const store = state || {};
const subscribe = (listen) => listeners.push(listen);
const dispatch = (action) => {
storeChange(store, action);
listeners.forEach(item => {
item(store);
})
};
return { store, dispatch, subscribe }
}
# index.js
···
const { store, dispatch, subscribe } = createStore(state, storeChange)
···
···
// 添加 listeners
subscribe((store) => renderApp(store));
renderApp(store);
dispatch({ type: 'BODY_TEXT', text: '我是調用 dispatch 修改的 body' });這樣,我們每次調用 dispatch 時,頁面就會重新刷新。如果我們不想刷新頁面,只想 alert 一句話,只需要更改添加的 listeners 就好了:
subscribe((store) => alert('頁面刷新了'));
renderApp(store);
dispatch({ type: 'BODY_TEXT', text: '我是調用 dispatch 修改的 body' });這樣我們就保證了 createStore 的通用性。
四、優(yōu)化
到這里,我們似乎已經實現(xiàn)了之前想達到的效果:我們實現(xiàn)了一個全局公用的 store , 而且這個 store 的修改是經過嚴格把控的,并且每次通過 dispatch 修改 store 后,都可以完成頁面的自動刷新。
可是,顯然這樣并不足夠,以上的代碼仍有些簡陋,存在嚴重的性能問題,
雖然我們只是修改了 body 的文案,可是,在頁面重新渲染時,head 也被再次渲染。那么,我們是不是可以在頁面渲染的時候,來對比新舊兩個 store 來感知哪些部分需要重新渲染,哪些部分不必再次渲染呢?
根據上面的想法,我們再次來修改我們的代碼:
# storeChange.js
export const storeChange = (store, action) => {
switch (action.type) {
case 'HEAD_COLOR':
return {
...store,
head: {
...store.head,
color: action.color
}
}
case 'BODY_TEXT':
return {
...store,
body: {
...store.body,
text: action.text
}
}
default:
return { ...store }
}
}
# createStore.js
export const createStore = (state, storeChange) => {
const listeners = [];
let store = state || {};
const subscribe = (listen) => listeners.push(listen);
const dispatch = (action) => {
const newStore = storeChange(store, action);
listeners.forEach(item => {
item(newStore, store);
})
store = newStore;
};
return { store, dispatch, subscribe }
}
# index.js
import { state } from './redux/state.js';
import { storeChange } from './redux/storeChange.js';
import { createStore } from './redux/createStore.js';
const { store, dispatch, subscribe } = createStore(state, storeChange);
function renderHead (state){
console.log('render head');
const head = document.getElementById('head')
head.innerText = state.text;
head.style.color = state.color;
}
function renderBody (state){
console.log('render body');
const body = document.getElementById('body')
body.innerText = state.text;
body.style.color = state.color;
}
function renderApp (store, oldStore={}){
if(store === oldStore) return;
store.head !== oldStore.head && renderHead(store.head);
store.body !== oldStore.body && renderBody(store.body);
console.log('render app',store, oldStore);
}
// 首次渲染
subscribe((store, oldStore) => renderApp(store, oldStore));
renderApp(store);
dispatch({ type: 'BODY_TEXT', text: '我是調用 dispatch 修改的 body' });以上,我們修改了 storeChange ,讓他不再直接修改原來的 store,而是通過計算,返回一個新的 store 。我們又修改了 cearteStore 讓他接收 storeChange 返回的新 store ,在 dispatch 修改數(shù)據并且頁面刷新后,把新 store 賦值給之前的 store 。而在頁面刷新時,我們來通過比較 newStore 和 oldStore ,感知需要重新渲染的部分,完成一些性能上的優(yōu)化。
最后
我們通過簡單的代碼例子,簡單了解下 redux,雖然代碼仍有些簡陋,可是我們已經實現(xiàn)了 redux 的幾個核心理念:
- 應用中的所有state都以一個object tree的形式存儲在一個單一的store中。
- 唯一能改store的方法是觸發(fā)action,action是動作行為的抽象。
到此這篇關于React手寫redux過程分步講解的文章就介紹到這了,更多相關React redux內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
react-beautiful-dnd 實現(xiàn)組件拖拽功能
這篇文章主要介紹了react-beautiful-dnd 實現(xiàn)組件拖拽功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
在create-react-app中使用css modules的示例代碼
這篇文章主要介紹了在create-react-app中使用css modules的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07
React操作真實DOM實現(xiàn)動態(tài)吸底部的示例
本篇文章主要介紹了React操作真實DOM實現(xiàn)動態(tài)吸底部的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
React+ResizeObserver實現(xiàn)自適應ECharts圖表
ResizeObserver是JavaScript的一個API,用于監(jiān)測元素的大小變化,本文主要為大家介紹了React如何利用ResizeObserver實現(xiàn)自適應ECharts圖表,需要的可以參考下2024-01-01
詳解Webpack+Babel+React開發(fā)環(huán)境的搭建的方法步驟
本篇文章主要介紹了詳解Webpack+Babel+React開發(fā)環(huán)境的搭建的方法步驟,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01

