提起 Redux 咱們想到最多的應該就是 React-redux 這個庫,但是實際上 Redux 和 React-redux 並非同一個東西, Redux 是一種架構模式,源於 Flux。具體介紹請看這裏,或者這裏,或者還有這裏。 React-redux 是 Redux 思想與 React 結合的一種具體實現。
在咱們使用 React 的時候,經常會遇到組件深層次嵌套且須要值傳遞的狀況,若是使用 props 進行值的傳遞,顯然是很是痛苦的。爲了解決這個問題,React 爲咱們提供了原生的 context API,但咱們用的最多的解決方案倒是使用 React-redux 這個基於 context API 封裝的庫。
本文並不介紹 React-redux 的具體用法,而是經過一個小例子,來了解下什麼是 redux。html
好了,如今咱們言歸正傳,來實現咱們本身的 redux。react
首先,咱們用 creat-react-app 來建立一個項目,刪除 src 下冗餘部分,只保留 index.js,並修改 index.html 的 DOM 結構:git
# index.html
<div id="root">
<div id="head"></div>
<div id="body"></div>
</div>
複製代碼
咱們在 index.js 中建立一個對象,用它來儲存、管理咱們整個應用的數據狀態,並用渲染函數把數據渲染在頁面:github
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 中已經出現了紅色字體的‘我是頭部’,在 body 中出現了綠色字體的‘我是body’。 redux
若是咱們把 head 和 body 看做是 root 中的兩個組件,那麼咱們已經實現了一個全局惟一的 state 。這個 state 是全局共享的,隨處可調用的。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';
}
複製代碼
如今看來,在咱們面前出現了一個矛盾:咱們須要數據共享,但共享數據被任意的修改又會形成不可預期的問題!
爲了解決這個矛盾,咱們須要一個管家,專門來管理共享數據的狀態,任何對共享數據的操做都要經過他來完成,這樣,就避免了隨意修改共享數據帶來的不可預期的危害! 咱們從新定義一個函數,用這個函數充當咱們的管家,來對咱們的共享數據進行管理:segmentfault
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 的渲染函數:數組
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 函數接收兩個參數,一個是須要修改的 state ,另外一個是修改的值。這時,雖然咱們依舊修改了 state ,可是經過 dispatch 函數,咱們使這種改變變得可控,由於任何改變 state 的行爲,咱們均可以在 dispatch 中找到改變的源頭。 這樣,咱們彷佛已經解決了以前的矛盾,咱們建立了一個全局的共享數據,並且嚴格的把控了任何改變這個數據的行爲。
然而,在一個文件中,咱們既要保存 state, 還要維護管家函數 dispatch,隨着應用的愈來愈複雜,這個文件勢必會變得冗長繁雜,難以維護。
如今,咱們把 state 和 dispatch 單獨抽離出來:bash
這樣,不只使單個文件變得更加精簡,並且在其餘的應用中,咱們也能夠很方便的複用咱們這套方法,只須要傳入不一樣應用的 state 和修改 state 的對應邏輯 stateChange,就能夠放心的經過調用 dispatch 方法,對數據進行各類操做了:架構
# 改變咱們的目錄結構,新增 redux 文件夾
+ src
++ redux
--- state.js // 儲存應用數據狀態
--- storeChange.js // 維護一套修改 store 的邏輯,只負責計算,返回新的 store
--- createStore.js // 結合 state 和 stateChange , 建立 store ,方便任何應用引用
--index.js
## 修改後的各個文件
# state.js -- 全局狀態
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 -- 建立全局 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);
複製代碼
經過以上的文件拆分,咱們看到,不只使單個文件更加精簡,文件的職能也更加明確:app
一切彷佛都那麼美好,但是當咱們在首次渲染後調用 dispatch 修改 store 時,咱們發現,雖然數據被改變了,但是頁面並無刷新,只有在 dispatch 改變數據後,從新調用 renderApp() 才能實現頁面的刷新。
// 首次渲染
renderApp(store);
dispatch({ type: 'BODY_TEXT', text: '我是調用 dispatch 修改的 body' }) // 修改數據後,頁面並無自動刷新
renderApp(store); // 從新調用 renderApp 頁面刷新
複製代碼
這樣,顯然並不能達到咱們的預期,咱們並不想在每次改變數據後手動的刷新頁面,若是能在改變數據後,自動進行頁面的刷新,固然再好不過了!
若是直接把 renderApp 寫在 dispatch 裏,顯然是不太合適的,這樣咱們的 createStore 就失去了通用性。
咱們能夠在 createStore 中新增一個收集數組,把 dispatch 調用後須要執行的方法統一收集起來,而後再循環執行,這樣,就保證了 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 的通用性。
到這裏,咱們彷佛已經實現了以前想達到的效果:咱們實現了一個全局公用的 store , 並且這個 store 的修改是通過嚴格把控的,而且每次經過 dispatch 修改 store 後,均可以完成頁面的自動刷新。
但是,顯然這樣並不足夠,以上的代碼仍有些簡陋,存在嚴重的性能問題,咱們在 render 函數中打印日誌能夠看到:
# 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 修改數據而且頁面刷新後,把新 store 賦值給以前的 store 。而在頁面刷新時,咱們來經過比較 newStore 和 oldStore ,感知須要從新渲染的部分,完成一些性能上的優化。
從新打開控制檯,咱們能夠看到,在咱們修改 body 時,head 並無從新渲染:
咱們經過簡單的代碼例子,簡單瞭解下 redux,雖然代碼仍有些簡陋,但是咱們已經實現了 redux 的幾個核心理念:
以上,是本身對《React.js 小書》的讀後總結,限於篇幅,在下篇中,咱們再來結合 react ,實現本身的 react-redux。