顫抖吧!一起手寫一個redux框架!
redux是一個前端架構
,經常和react一起使用。你要用react.js基本上都要用到redux和react-redux,但這兩者並不是一個東西!
- redux是一個前端框架,你可以把它用到react、vue,設定jquery。
- react-redux是把redux這個前端架構結合到react形成的庫,就是
redux架構
在react中的體現。
話不多說,我們來從頭手寫一個redux框架。
用create-react-app
新建一個專案myRedux
,修改public/index.html
裡的body
結構為:
<body>
<div id='title'></div>
<div id='content'></div>
</body>
把src/index.js
裡的程式碼替換為如下程式碼,代表我們的應用狀態:
const appState = {
title: {
text: 'React.js 小書',
color: 'red',
},
content: {
text: 'React.js 小書內容',
color: 'blue'
}
}
我們新增幾個渲染函式,它會把上面的狀態渲染到頁面上:
function renderApp (appState) {
renderTitle(appState.title)
renderContent(appState.content)
}
function renderTitle (title) {
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = title.text
titleDOM.style.color = title.color
}
function renderContent (content) {
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = content.text
contentDOM.style.color = content.color
}
很簡單,renderApp
方法會呼叫renderTitle
和renderContent
,而這兩個方法會把appState
的資料渲染到頁面上。
執行截圖貼在這裡
一. 手寫dispatch
這是一個很簡單的頁面,但是存在著嚴重的隱患:我們渲染頁面時,用到了一個共享資料appState,每個人都在哪裡都可以修改它。如果在渲染之前做了一系列其他操作:
loadDataFromServer()
doSomethingUnexpected()
doSomthingMore()
// ...
renderApp(appState)
你根本無法知道這些方法會對renderApp做什麼修改。這種任何地方都可以對共享資料進行修改的寫法,會給debug/開發造成極大的難度。
有過一定開發經驗的朋友看到這裡,一定會忍不住對修改共享資料的操作做一個收口,讓所有對共享資料修改的操作統一收口在一起。
原來各個模組可以直接修改appState,如下圖:
而現在要寫更改appState,必須通過dispatch,如下圖:
我們定義一個收口函式dispatch
,讓它專門負責共享資料的修改:
function dispatch (action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
appState.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
appState.title.color = action.color
break
default:
break
}
}
所有對共享資料的修改,必須通過dispatch
函式。它接受一個引數action
,這個action
是一個普通的JavaScript物件
,裡面必須包含一個type
指明你想幹什麼。dispatch
在switch
裡會去識別這個type欄位
,然後對appState
進行相應的修改。
上面的dispatch只能識別兩種操作:一種是'UPDATE_TITLE_TEXT'
會用action
的text
欄位去更新appState.title.text
;一種是'UPDATE_TITLE_COLOR'
會用action
的color
欄位去更新appState.title.color
。可以看到,action裡除了type欄位,其他都是可以自定義的。
二. 手寫store
上面我們有了appState
和dispatch
。
現在我們進一步整合,把它們都集中在一個地方,並給這個地方起名叫store
,然後建立一個createStore
方法來構建store
。
function createStore (state, stateChanger) {
const getState = () => state
const dispatch = (action) => stateChanger(state, action)
return { getState, dispatch }
}
createStore
接受兩個引數,一個表示app的狀態state,一個用來修改state。
現在,我們來修改原來的程式碼:
let appState = {
title: {
text: 'React.js 小書',
color: 'red',
},
content: {
text: 'React.js 小書內容',
color: 'blue'
}
}
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
state.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
state.title.color = action.color
break
default:
break
}
}
const store = createStore(appState, stateChanger)
renderApp(store.getState()) // 首次渲染頁面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小書》' }) // 修改標題文字
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改標題顏色
renderApp(store.getState()) // 把新的資料渲染到頁面上
針對不同的app,我們給createStore
傳入初始狀態appState
和用於描述appState
變化的stateChanger
。需要修改的資料的時候通過store.dispatch
,需要獲取資料的時候通過store.getState
。
監控資料的變化
上面的程式碼有一個問題,在dispatch
修改資料的時候,其實只是資料發生了變化,並沒有呼叫renderApp
方法,頁面上的內容是不會變化的。然而,我們又不能每次dispatch
的時候又renderApp
,我們希望用一種通用的監聽資料變化的方式,然後重新渲染頁面。
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
stateChanger(state, action)
listeners.forEach((listener) => listener())
}
return { getState, dispatch, subscribe }
}
我們在createStore中定義一個陣列listeners,並對外提供一個subscribe方法,可以用該方法給陣列push一個渲染函式
,每當dispatch
的時候,listeners
裡的渲染函式都會被呼叫。這樣我們就可以在資料變化的時候重新渲染頁面:
const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState()))
renderApp(store.getState()) // 首次渲染頁面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小書》' }) // 修改標題文字
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改標題顏色
三. 手寫reducer
細心的朋友會發現,前面的程式碼有著嚴重的效能問題。我們在每個渲染函式前打下log:
function renderApp (appState) {
console.log('render app...')
renderTitle(appState.title)
renderContent(appState.content)
}
function renderTitle (title) {
console.log('render title...')
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = title.text
titleDOM.style.color = title.color
}
function renderContent (content) {
console.log('render content...')
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = content.text
contentDOM.style.color = content.color
}
我們接下來dispatch兩個action,來修改title的color和text:
const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState())) // 監聽資料變化
renderApp(store.getState()) // 首次渲染頁面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《React.js 小書》' }) // 修改標題文字
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改標題顏色
可以在控制檯看到:
整個頁面都重新整理了三遍!第一次是頁面初始化,後面兩次修改title的狀態的操作,把整個頁面都重新整理了!
但其實,我們只希望讓跟修改的資料(title)有關係的元件重新整理。
這裡給出一個解決方案:
每次渲染之前,先對新資料和舊資料做一下比較,把不一樣的部分進行渲染。
function renderApp (newAppState, oldAppState = {}) { // 防止 oldAppState 沒有傳入,所以加了預設引數 oldAppState = {}
if (newAppState === oldAppState) return // 資料沒有變化就不渲染了
console.log('render app...')
renderTitle(newAppState.title, oldAppState.title)
renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle = {}) {
if (newTitle === oldTitle) return // 資料沒有變化就不渲染了
console.log('render title...')
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = newTitle.text
titleDOM.style.color = newTitle.color
}
function renderContent (newContent, oldContent = {}) {
if (newContent === oldContent) return // 資料沒有變化就不渲染了
console.log('render content...')
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = newContent.text
contentDOM.style.color = newContent.color
}
我們修改下stateChanger
,讓它接收到action
後不是直接去修改state
,而是重新生成一個物件。
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
return { // 構建新的物件並且返回
...state,
title: {
...state.title,
text: action.text
}
}
case 'UPDATE_TITLE_COLOR':
return { // 構建新的物件並且返回
...state,
title: {
...state.title,
color: action.color
}
}
default:
return state // 沒有修改,返回原來的物件
}
}
因為我們改了stateChanger
,我們來修改下createStore
。
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = stateChanger(state, action) // 覆蓋原物件
listeners.forEach((listener) => listener())
}
return { getState, dispatch, subscribe }
}
此時,我們再去執行剛才的程式碼:
我們就這樣成功優化了頁面效能,每次只重新整理了修改過的資料對應的元件。
此時,我們的stateChanger還有沒有優化的空間了?
其實,可以把appState和stateChanger合併到一起:
function stateChanger (state, action) {
if (!state) {
return {
title: {
text: 'React.js 小書',
color: 'red',
},
content: {
text: 'React.js 小書內容',
color: 'blue'
}
}
}
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
return {
...state,
title: {
...state.title,
text: action.text
}
}
case 'UPDATE_TITLE_COLOR':
return {
...state,
title: {
...state.title,
color: action.color
}
}
default:
return state
}
}
這樣createStore就只有了一個引數:
function createStore (stateChanger) {
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = stateChanger(state, action)
listeners.forEach((listener) => listener())
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
此時,這個stateChanger
,就是redux
的reducer
了!
四. 總結
此時,我們已經完整地手寫了一個redux框架。
redux的核心就是發明了store,通過dispatch一個action來更改store裡的值。
五. 參考
https://imweb.io/topic/59f5a7fdb72024f03c7f49bc
http://huziketang.mangojuice.top/books/react/lesson35
相關文章
- 自己寫一個mvc框架吧(一)MVC框架
- 自己寫一個mvc框架吧(四)MVC框架
- 自己寫一個mvc框架吧(五)MVC框架
- 自己寫一個mvc框架吧(二)MVC框架
- 自己寫一個mvc框架吧(三)MVC框架
- 真二叉樹,程式猿們顫抖吧二叉樹
- Flutter抖動動畫、顫抖動畫、Flutter文字抖動效果Flutter動畫
- 寫一個小框架框架
- 規約模式,顫抖吧產品經理!再也不怕你亂改需求了模式
- 如何編寫一個 Redux 中介軟體Redux
- 不如自己寫一個 schema 類庫吧
- 寫了一個 WebSocket 框架Web框架
- 揭開Redux神祕面紗:手寫一個min-ReduxRedux
- 寫一個菜鳥裹裹小程式吧
- 我寫了個BoardView,看一下吧。View
- 博弈論——顫抖手納什均衡(二十一)
- 一個被寫爛的redux計數小例子Redux
- 封裝一個在react上更易用的redux框架封裝ReactRedux框架
- 為何 Canvas 內元素動畫總是在顫抖?Canvas動畫
- 一個外掛讓你在 Redux 中寫 promise 事半功倍ReduxPromise
- react 知識梳理(二):手寫一個自己的 reduxReactRedux
- 編寫你人生中第一個機器學習程式碼吧!機器學習
- 為何使用Canvas內元素動畫總是在顫抖?Canvas動畫
- 寫一個最簡陋的node框架框架
- 仿照dubbo手寫一個RPC框架RPC框架
- 自己動手寫一個持久層框架框架
- 嘗試手寫一個註解框架框架
- 手寫指令碼程式碼太累!搞一個生成工具吧指令碼
- 一起學習造輪子(二):從零開始寫一個ReduxRedux
- 當滑鼠、耳機和鍵盤一起顫抖:遊戲中的力反饋究竟有多重要?遊戲
- do 一下來了一個 reduxRedux
- 手挽手帶你學React:四檔(上)一步一步學會react-redux (自己寫個Redux)ReactRedux
- 從零開始學React:四檔(上)一步一步學會react-redux (自己寫個Redux)ReactRedux
- React 知識梳理(三):手寫一個自己的 React-reduxReactRedux
- 一款簡單的訊息防抖框架框架
- 深度:從零編寫一個微前端框架前端框架
- 寫一個最簡陋的node框架(2)框架
- 自己手寫一個SpringMVC框架(簡化)SpringMVC框架