這一次總結主要是針對 React 的亮點 Redux 。html
在什麼樣的狀況下適合使用 React-redux:redux
Redux 是一種架構模式,有三大特性 Store、Actions、Reducers , Store 是保存數據的地方,整個應用只能有一個。Actions 是
view
發出的通知,監聽state
的變化。 Reducers 是一個純函數,返回新的state
。數組
優雅地修改共享狀態。bash
const appState = {
title: {
text: '這裏是標題',
color: 'red',
},
content: {
text: '內容內容內容',
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(appState)
複製代碼
上面的
appState
是一個共享狀態,隨時會被修改,這是不可預料的,有時候一些模塊須要這個共享狀態而且還須要修改它,這時能夠聲明一個函數來監聽哪一處修改數據。架構
// 監聽應用狀態修改
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({ type: 'UPDATE_TITLE_TEXT', text: '我已經修改了標題和顏色' }) // 修改標題文本
dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'green' }) // 修改標題顏色
renderApp(appState) // 渲染
複製代碼
這樣就不怕
renderApp()
執行以前的其餘函數會對它們作什麼修改,若是某個函數修改了title.text
可是我並不想要它這麼幹,我須要debug
出來是哪一個函數修改了,我只須要在dispatch
的switch
的第一個case
內部打個斷點就能夠調試出來了。app
抽離Store。函數
接下來會把
appState
和dispatch
抽出來放在一個地方,把這個地方叫做store
,而且構建一個函數叫createStore
用來專門生產這種state
和dispatch
的集合。post
<!-- 接受一個 應用狀態 和 描述應用程序狀態函數( 至關上面的dispatch ) -->
function createStore (state, stateChanger) {
<!-- 直接返回 state -->
const getState = () => state
<!-- 接受一個 state 還有一個 根據 action 來修改 state 的函數( 至關上面的dispatch ) -->
const dispatch = (action) => stateChanger(state, action)
<!-- 返回 state 和 dispatch -->
return { getState, dispatch }
}
複製代碼
以後咱們來使用它:性能
<!-- 監聽應用狀態修改 (至關於上面的 dispatch 函數) -->
<!-- 接受 應用狀態 和 修改應用狀態的信息 -->
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
}
}
// store
function createStore(state, stateChanger) {
const getState = () => state;
const dispatch = (action) => stateChanger(state, action);
return {getState, dispatch}
}
<!-- 傳入 應用狀態 和 修改應用狀態的函數 -->
const store = createStore(appState, stateChanger);
renderApp(store.getState()) // 第一次渲染
store.dispatch({type: 'UPDATE_TITLE_TEXT', text: '老子修改了標題'}); // 發送 action
store.dispatch({type: 'UPDATE_TITLE_COLOR', color: 'green'}); // 發送 action
renderApp(store.getState()) // 從新渲染
複製代碼
上面每次經過
dispatch
修改數據時候,若是不手動調用renderApp
頁面是不會發生變化的,咱們能夠利用觀察者模式,往dispatch
加入renderApp
就好了。ui
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}
}
const store = createStore(appState, stateChanger)
<!-- 下面能夠傳遞多個渲染函數,而後調用 dispatch 所有會從新渲染 -->
store.subscribe(() => renderApp(store.getState()))
store.subscribe(() => renderApp2(store.getState()))
store.subscribe(() => renderApp3(store.getState()))
複製代碼
上面
subscribe
是訂閱者,這樣的訂閱模式,不管你利用dispatch
進行修改數據,他都會從新渲染,還有一個好處就是拿同一塊數據來渲染別的頁面時,dispatch
致使的變化也會讓每一個頁面都從新渲染。
總結來講就是構建一個
createStore
,它能夠產生一種咱們新定義的數據類型store
,經過store.getState
咱們獲取共享狀態,並且咱們約定只能經過store.dispatch
修改共享狀態。store
也容許咱們經過store.subscribe
監聽數據數據狀態被修改了,而且進行後續的例如從新渲染頁面的操做。
共享結構的對象提升性能。
// 渲染函數
function renderApp(appState) {
console.log('渲染所有')
renderTitle(appState.title)
renderContent(appState.content)
}
// 渲染標題
function renderTitle(title) {
console.log('渲染標題')
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = title.text
titleDOM.style.color = title.color
}
// 渲染內容
function renderContent(content) {
console.log('渲染內容')
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = content.text
contentDOM.style.color = content.color
}
renderApp(store.getState()) // 第一次渲染
store.dispatch({type: 'UPDATE_TITLE_TEXT', text: '老子修改了標題'});
store.dispatch({type: 'UPDATE_TITLE_COLOR', color: 'green'});
複製代碼
在上面調用時會從新渲染三次,這會有一個很嚴重的性能問題,我並無修改
content
的東西,也會從新渲染content
。
這裏提出的解決方案是,在每一個渲染函數執行渲染操做以前先作個判斷,判斷傳入的新數據和舊的數據是否是相同,相同的話就不渲染了。
// 渲染函數
function renderApp(newAppState, oldAppState = {}) {
if (newAppState === oldAppState) return // 數據沒有變化就不渲染了
console.log('渲染所有')
renderTitle(newAppState.title, oldAppState.title)
renderContent(newAppState.content, oldAppState.content)
}
// 渲染標題
function renderTitle(newTitle, oldTitle = {}) {
if (newTitle === oldTitle) return // 數據沒有變化就不渲染了
console.log('渲染標題')
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = newTitle.text
titleDOM.style.color = newTitle.color
}
// 渲染內容
function renderContent(newContent, oldContent = {}) {
if (newContent === oldContent) return // 數據沒有變化就不渲染了
console.log('渲染內容')
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = newContent.text
contentDOM.style.color = newContent.color
}
// 監聽應用狀態修改
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
}
}
// store
function createStore(state, stateChanger) {
const listeners = [];
const subscribe = (listener) => listeners.push(listener);
const getState = () => state;
const dispatch = (action) => {
state = stateChanger(state, action); // 從新覆蓋 state
listeners.forEach((listener) => listener());
}
return { getState, dispatch, subscribe }
}
const store = createStore(appState, stateChanger);
let oldState = store.getState(); // 保存舊狀態
store.subscribe(() => {
const newState = store.getState();
renderApp(newState, oldState);
oldState = newState; // 每次更改讓舊的狀態變成新的狀態
})
renderApp(store.getState()) // 第一次渲染
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '老子修改了標題' });
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'green' });
複製代碼
上面那三個是第一次渲染觸發的,下面那兩次修改從新渲染時並無從新渲染
content
。
因爲上面須要保存一箇舊的狀態,而後根據舊的狀態和新的狀態進行判斷,上面的
stateChanger
修改爲返回一個新的狀態,這個新的狀態與舊的狀態是相對獨立的,還有就是store
裏面的dispatch
,每次進行修改就用一個state
接受一個新的狀態而且覆蓋原來的state
,最後再將新的狀態和舊的狀態傳達給renderApp
。
reducer
reducer
是一個純函數,它接受兩個參數,一個是state
,一個是action
,它的做用是計算state
而且返回一個新的state
,若是沒有傳入值或沒法識別action
時,會直接返回state
。
function themeReducer (state, action) {
if (!state) return {
themeName: 'Red Theme',
themeColor: 'red'
}
switch (action.type) {
case 'UPATE_THEME_NAME':
return { ...state, themeName: action.themeName }
case 'UPATE_THEME_COLOR':
return { ...state, themeColor: action.themeColor }
default:
return state
}
}
function createStore(reducer) {
let state = null; // 初始化狀態
const listeners = [];
const subscribe = (listener) => listeners.push(listener);
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
}
dispatch({}) // 初始化
return { getState, dispatch, subscribe }
}
複製代碼
Store
收到Action
之後,必須給出一個新的State
,這樣View
纔會發生變化。這種State
的計算過程就叫作Reducer
。Reducer
是一個純函數,它接受Action
和當前State
做爲參數,返回一個新的State
。
redux
的使用有如下步驟:
// 定一個 reducer
function reducer (state, action) {
/* 初始化 state 和 switch case */
}
// 生成 store
const store = createStore(reducer)
// 監聽數據變化從新渲染頁面
store.subscribe(() => renderApp(store.getState()))
// 首次渲染頁面
renderApp(store.getState())
// 後面能夠隨意 dispatch 了,頁面自動更新
store.dispatch(...)
複製代碼