redux
是什麼?javascript
redux
是一個狀態管理器css
redux
是一個狀態管理器,那麼狀態是什麼?java
狀態就是數據,狀態管理器也就是數據管理器。那麼咱們來實現一個簡單的狀態管理器吧。css3
state
變量吧,做爲存儲狀態的地方。let state = {
name: '混沌傳奇',
}
//使用下狀態
console.log(state.name)
複製代碼
updateState
吧。let state = {
name: '混沌傳奇',
}
//使用下狀態
console.log(state.name) // 輸出:'混沌傳奇'
const updateState = (newName) => {
state.name = newName
}
//更新下狀態
updateState('混沌傳奇2')
//再打印下狀態
console.log(state.name) //輸出:'混沌傳奇2'
複製代碼
好,問題有了,那麼咱們就解決問題,咱們可使用發佈-訂閱模式來解決這個問題。git
let listeners = []
let state = {
name: '混沌傳奇',
}
//使用下狀態
console.log(state.name) // 輸出:'混沌傳奇'
const updateState = (newName) => {
state.name = newName
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
//訂閱下狀態
subscribe(() => {
//在state更新的時候,打印下state.name
console.log(`最新的name是:${state.name}`) //輸出:'最新的name是:混沌傳奇2'
})
//更新下狀態
updateState('混沌傳奇2')
//再打印下狀態
console.log(state.name) //輸出:'混沌傳奇2'
複製代碼
state
數據的訂閱問題,咱們再看下已經實現的這個簡單的狀態管理器,是否是更新狀態的updateState
方法只能更新state.name
,咱們指望更新狀態的updateState
方法能夠更新state
中存儲的全部數據,不僅是name
。咱們來解決下這個問題。let listeners = []
let state = {
name: '',
like: '',
}
const updateState = (newState) => {
state = newState
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
複製代碼
好了,咱們升級了下updateState
方法,咱們試用下咱們新的狀態管理器github
let listeners = []
let state = {
name: '',
like: '',
}
const updateState = (newState) => {
state = newState
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
subscribe(() => {
//在state更新的時候,打印下state
console.log(state)
})
updateState({
...state,
name: '混沌傳奇'
})
updateState({
...state,
like: '打乒乓球'
})
複製代碼
輸出依次爲:編程
> {name: "混沌傳奇", like: ""}
> {name: "混沌傳奇", like: "打乒乓球"}
複製代碼
updateState
方法以後,咱們把咱們的狀態管理器再封裝一下吧,如今的listeners
和state
都暴露在外面,容易被別人更改const createStore = (initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const updateState = (newState) => {
state = newState
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
updateState,
subscribe,
}
}
複製代碼
咱們來使用這個狀態管理器來管理下更多的數據試試:redux
const createStore = (initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const updateState = (newState) => {
state = newState
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
updateState,
subscribe,
}
}
let initState = {
name: '',
sex: '',
age: '',
like: '',
friend: '',
}
let { getState, updateState, subscribe } = createStore(initState)
subscribe(() => {
console.log(getState())
})
updateState({
...getState(),
name: '混沌傳奇',
})
updateState({
...getState(),
sex: '男',
})
updateState({
...getState(),
age: '25',
})
updateState({
...getState(),
like: '打羽毛球',
})
updateState({
...getState(),
friend: '阿龍',
})
複製代碼
運行代碼,輸出依次爲:app
> {name: "混沌傳奇", sex: "", age: "", like: "", friend: ""}
> {name: "混沌傳奇", sex: "男", age: "", like: "", friend: ""}
> {name: "混沌傳奇", sex: "男", age: "25", like: "", friend: ""}
> {name: "混沌傳奇", sex: "男", age: "25", like: "打羽毛球", friend: ""}
> {name: "混沌傳奇", sex: "男", age: "25", like: "打羽毛球", friend: "阿龍"}
複製代碼
到這裏咱們完成了一個簡單的狀態管理器。 固然離真正的redux還差很遠,下面咱們繼續完善咱們的狀態管理器。ide
咱們用上面咱們實現的狀態管理器來實現一個自增自減程序吧,指望count
是一個自增|自減的number
類型的數值:
const createStore = (initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const updateState = (newState) => {
state = newState
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
updateState,
subscribe,
}
}
let initState = {
count: 0
}
let { getState, updateState, subscribe } = createStore(initState)
console.log(getState())
subscribe(() => {
console.log(getState())
})
//自增
updateState({
...getState(),
count: getState().count+1,
})
//自減
updateState({
...getState(),
count: getState().count-1,
})
//隨便改下
updateState({
...getState(),
count: '傻了吧',
})
複製代碼
運行程序,輸出依次爲:
> {count: 0}
> {count: 1}
> {count: 0}
> {count: "傻了吧"}
複製代碼
從輸出咱們能夠看出,count
被修改爲了字符串類型,咱們指望是count
是number
類型,解決這個問題的辦法就是,咱們修改狀態的時候,來按照指望來修改狀態。
怎麼讓修改狀態的時候,按照咱們指望來修改呢?
咱們能夠這樣:
action
)reducer
),reducer
會接收上次更新後的state
和updateState
傳遞進來的最新的action
,reducer
根據action
來修改state
,修改完後,返回最新的state
給updateState
updateState
方法接收action
,把action
告訴reducer
......根據這3點,咱們來修改下咱們的狀態管理器
const createStore = (reducer, initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const updateState = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
updateState,
subscribe,
}
}
複製代碼
咱們來嘗試使用下新的 狀態管理器 來實現自增和自減
const createStore = (reducer, initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const updateState = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
updateState,
subscribe,
}
}
let initState = {
count: 0
}
let reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count+1,
}
case 'DECREMENT':
return {
...state,
count: state.count-1,
}
default:
return state
}
}
let { getState, updateState, subscribe } = createStore(reducer, initState)
console.log(getState())
subscribe(() => {
console.log(getState())
})
//自增
updateState({
type: 'INCREMENT',
})
//自減
updateState({
type: 'DECREMENT',
})
//隨便改下
updateState({
count: '傻了吧',
})
複製代碼
運行程序,輸出依次爲:
> {count: 0}
> {count: 1}
> {count: 0}
> {count: 0}
複製代碼
咱們能夠看到,打印輸出沒有了{count: "傻了吧"}
,改成了{count: 0}
爲了更加像真正的redux
,咱們再改下咱們的狀態管理器,咱們把updateState
改成dispatch
:
const createStore = (reducer, initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
dispatch,
subscribe,
}
}
複製代碼
到這裏爲止,咱們已經實現了一個有計劃的狀態管理器!
前面咱們管理的數據都比較少,咱們如今嘗試把管理的數據增長一些:
const createStore = (reducer, initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
dispatch,
subscribe,
}
}
let initState = {
studyPerson: {
name: '',
age: '',
like: [],
sex: '',
readedBooks: [],
},
playfulPerson: {
name: '',
age: '',
like: [],
sex: '',
readedBooks: [],
},
books: [],
}
let reducer = (state, action) => {
switch (action.type) {
case 'SET_STUDY_PERSON':
return {
...state,
studyPerson: action.data,
}
case 'SET_PLAYFUL_PERSON':
return {
...state,
playfulPerson: action.data,
}
case 'SET_BOOKS':
return {
...state,
books: action.data,
}
default:
return state
}
}
let { getState, dispatch, subscribe } = createStore(reducer, initState)
//設置圖書館擁有的圖書
dispatch({
type: 'SET_BOOKS',
data: [
{
id: 1,
name: 'javascript高級程序設計',
kind: '計算機編程',
},
{
id: 2,
name: '圖解css3',
kind: '計算機編程',
},
{
id: 3,
name: 'javascript函數式編程',
kind: '計算機編程',
},
{
id: 4,
name: '三國演義',
kind: '小說',
},
{
id: 5,
name: '籃球投籃技巧',
kind: '運動類',
},
],
})
//設置學習者信息
dispatch({
type: 'SET_STUDY_PERSON',
data: {
name: '混沌傳奇',
age: 25,
like: ['學習計算機知識', '打羽毛球'],
sex: '男',
readedBooks: [1, 2, 3],
},
})
//設置愛玩者信息
dispatch({
type: 'SET_PLAYFUL_PERSON',
data: {
name: '阿龍',
age: 28,
like: ['看綜藝', '看小說', '看雜誌'],
sex: '男',
readedBooks: [4, 5],
},
})
console.log(getState())
複製代碼
從這段代碼,咱們能夠看出,隨着管理的數據量的增長,咱們在reducer
中寫的邏輯就愈來愈多,並且,每次dispatch
執行的時候,傳給dispatch
的action
內容太多,閱讀起來有點麻煩,看到的是一大坨代碼。
怎麼優化一下呢?
咱們能夠這樣優化:
reducer
拆分紅多個reducer
,每一個子state
對應一個reducer
,studyPerson
、playfulPerson
、books
都是子state
。state
對應的reducer
合併成一個rootReducer
,而後把rootReducer
傳遞給createStore
action
能夠由actionCreater
生成,把action
歸類到不一樣的js
文件中,每一個js
文件export
出各個action
對應的actionCreater
。//文件 ./redux.js
const createStore = (reducer, initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
return {
getState,
dispatch,
subscribe,
}
}
export {
createStore,
}
//文件 ./reducers.js
import {
SET_STUDY_PERSON,
SET_PLAYFUL_PERSON,
SET_BOOKS,
} from './actions.js'
let studyPersonReducer = (state, action) => {
switch (action.type) {
case SET_STUDY_PERSON:
return action.data
default:
return state
}
}
let playfulPersonReducer = (state, action) => {
switch (action.type) {
case SET_PLAYFUL_PERSON:
return action.data
default:
return state
}
}
let booksReducer = (state, action) => {
switch (action.type) {
case SET_BOOKS:
return action.data
default:
return state
}
}
export {
studyPersonReducer,
playfulPersonReducer,
booksReducer,
}
//文件 ./actions.js
export const SET_STUDY_PERSON = 'SET_STUDY_PERSON'
export const SET_PLAYFUL_PERSON = 'SET_PLAYFUL_PERSON'
export const SET_BOOKS = 'SET_BOOKS'
export const setStudyPerson = (data) => {
return {
type: SET_STUDY_PERSON,
data,
}
}
export const setPlayfulPerson = (data) => {
return {
type: SET_PLAYFUL_PERSON,
data,
}
}
export const setBook = (data) => {
return {
type: SET_BOOKS,
data,
}
}
//文件 ./index.js
import { createStore } from './redux.js'
import {
studyPersonReducer,
playfulPersonReducer,
booksReducer,
} from './reducers.js'
import {
setStudyPerson,
setPlayfulPerson,
setBook,
} from './actions.js'
let initState = {
studyPerson: {
name: '',
age: '',
like: [],
sex: '',
readedBooks: [],
},
playfulPerson: {
name: '',
age: '',
like: [],
sex: '',
readedBooks: [],
},
books: [],
}
let rootReducer = (state, action) => {
return {
studyPerson: studyPersonReducer(state.studyPerson, action),
playfulPerson: playfulPersonReducer(state.playfulPerson, action),
books: booksReducer(state.books, action),
}
}
let { getState, dispatch, subscribe } = createStore(rootReducer, initState)
//設置圖書館擁有的圖書
dispatch(setBook([
{
id: 1,
name: 'javascript高級程序設計',
kind: '計算機編程',
},
{
id: 2,
name: '圖解css3',
kind: '計算機編程',
},
{
id: 3,
name: 'javascript函數式編程',
kind: '計算機編程',
},
{
id: 4,
name: '三國演義',
kind: '小說',
},
{
id: 5,
name: '籃球投籃技巧',
kind: '運動類',
},
]))
//設置學習者信息
dispatch(setStudyPerson({
name: '混沌傳奇',
age: 25,
like: ['學習計算機知識', '打羽毛球'],
sex: '男',
readedBooks: [1, 2, 3],
}))
//設置愛玩者信息
dispatch(setPlayfulPerson({
name: '阿龍',
age: 28,
like: ['看綜藝', '看小說', '看雜誌'],
sex: '男',
readedBooks: [4, 5],
}))
console.log(getState())
複製代碼
拆分完reducer
,而且把action
按類劃分到單獨的js
文件中後,咱們的index.js
看起來要稍微簡略一些了,可是好像仍是有不少代碼。咱們看看還有能夠封裝的地方嗎?
仔細看咱們剛纔實現的代碼,初始化state
的代碼佔用了一大坨區域,合併子reducer
的代碼好像也能夠抽離爲工具方法
reducer
的代碼前面咱們實現的合併子reducer
的代碼以下:
...
let rootReducer = (state, action) => {
return {
studyPerson: studyPersonReducer(state.studyPerson, action),
playfulPerson: playfulPersonReducer(state.playfulPerson, action),
books: booksReducer(state.books, action)
}
}
...
複製代碼
能夠看到咱們是定義了一個rootReducer
函數,函數返回一個state
對象,對象每一個屬性的值對應一個子reducer
的執行,子reducer
執行完畢返回的就是屬性對於的子state
值。
咱們想一想一下,咱們是否能夠實現一個方法(方法就叫combineReducers
),這個方法只接收子reducer
,返回一個rootReducer
,這樣咱們只須要調用這個方法,就能夠生成rootReducer
了,好比:
const rootReducer = combineReducers({
studyPerson: studyPersonReducer,
playfulPerson: playfulPersonReducer,
books: booksReducer,
})
複製代碼
咱們嘗試實現下combineReducers
函數:
const combineReducers = (reducers) => {
const reducerKeys = Object.keys(reducers)
return (state, action) => {
let newState = {}
reducerKeys.forEach(key => {
newState[key] = reducers[key](state[key], action)
})
return newState
}
}
複製代碼
咱們把reducer
拆分爲一個一個的子reducer
,經過 combineReducers
合併了起來。可是還有個問題,state
咱們仍是寫在一塊兒的,這樣會形成state
樹很龐大,不直觀,很難維護。咱們須要拆分,一個state
,一個 reducer
寫一塊。
咱們先修改下reducers.js
:
//文件 ./reducers.js
import {
SET_STUDY_PERSON,
SET_PLAYFUL_PERSON,
SET_BOOKS
} from './actions.js'
let initStudyPerson = {
name: '',
age: '',
like: [],
sex: '',
readedBooks: [],
}
let studyPersonReducer = (state = initStudyPerson, action) => {
switch (action.type) {
case SET_STUDY_PERSON:
return action.data
default:
return state
}
}
let initPlayfulPerson = {
name: '',
age: '',
like: [],
sex: '',
readedBooks: [],
}
let playfulPersonReducer = (state = initPlayfulPerson, action) => {
switch (action.type) {
case SET_PLAYFUL_PERSON:
return action.data
default:
return state
}
}
let booksReducer = (state = [], action) => {
switch (action.type) {
case SET_BOOKS:
return action.data
default:
return state
}
}
export {
studyPersonReducer,
playfulPersonReducer,
booksReducer,
}
複製代碼
再修改下redux.js
,createStore
中增長一行 dispatch({ type: Symbol() })
,把combineReducers
方法放在redux.js
文件中
//文件 ./redux.js
const createStore = (reducer, initState) => {
let listeners = []
let state = initState || {}
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
//注意:Symbol() 不跟任何值相等,因此 type 爲 Symbol(),則 reducer 不匹配任何 type,走 default 邏輯,返回初始化 state 值,這樣就達到了初始化 state 的效果。
dispatch({ type: Symbol() })
return {
getState,
dispatch,
subscribe,
}
}
const combineReducers = (reducers) => {
const reducerKeys = Object.keys(reducers)
return (state, action) => {
let newState = {}
reducerKeys.forEach(key => {
newState[key] = reducers[key](state[key], action)
})
return newState
}
}
export {
createStore,
combineReducers,
}
複製代碼
本小節完整源碼見 demo-1
到這裏爲止,咱們的redux
已經實現的差很少啦!
什麼是Redux
中間件?
Redux
的中間件能夠理解爲是對dispatch
方法的包裝,加強dispatch
的功能。
假如咱們須要在每次redux
修改state
的時候,打印下修改前的state
和修改後的state
以及觸發修改state
的action
;
咱們能夠暫時這麼包裝下dispatch
:
import { createStore, combineReducers } from './redux.js'
const rootReducer = combineReducers({
count: countReducer
})
const store = createStore(rootReducer);
const next = store.dispatch;
store.dispatch = (action) => {
console.log('previous state:', store.getState())
next(action)
console.log('next state:', store.getState())
console.log('action:', action)
}
store.dispatch({
type: 'INCREMENT'
});
複製代碼
咱們來運行下代碼,輸出結果是:
> previous state: {count: 0}
> next state: {count: 0}
> action: {type: "INCREMENT"}
複製代碼
從輸出結果能夠看出,咱們實現了對dispatch
的加強。在改變state
的時候,正確的打印出了改變state
前的state
值,改變後的state
的值,以及觸發state
改變的action
。
dispatch
上面咱們實現了一個加強dispatch
需求,若是如今咱們在原有需求的基礎上,又有了一個新的加強需求。好比:我想在每次state
改變的時候,打印下當前時間。
嘗試實現下代碼:
const store = createStore(rootReducer);
const next = store.dispatch;
store.dispatch = (action) => {
console.log('state change time:', new Date().toLocaleString())
console.log('previous state:', store.getState())
next(action)
console.log('next state:', store.getState())
console.log('action:', action)
}
複製代碼
若是咱們,如今又有新的加強需求了,要記錄每次改變state時出錯的緣由,咱們再改下加強代碼:
const store = createStore(rootReducer);
const next = store.dispatch;
store.dispatch = (action) => {
try {
console.log('state change time:', new Date().toLocaleString())
console.log('previous state:', store.getState())
next(action)
console.log('next state:', store.getState())
console.log('action:', action)
} catch (e) {
console.log('錯誤信息:', e)
}
}
複製代碼
若是又雙叒叕有新的加強需求了呢。。。。。。
是否是一直這麼該加強代碼,很麻煩,到時候dispatch函數會愈來愈龐大,很難維護。因此這個方式不可取,咱們要考慮如何實現擴展性更強的多中間件包裝dispatch的方法。
const store = createStore(rootReducer);
const next = store.dispatch;
const loggerMiddleware = (action) => {
console.log('previous state:', store.getState())
next(action)
console.log('next state:', store.getState())
console.log('action:', action)
}
const updateTimeMiddleware = (action) => {
console.log('state change time:', new Date().toLocaleString())
loggerMiddleware(action)
}
const exceptionMiddleware = (action) => {
try {
updateTimeMiddleware(action)
} catch (e) {
console.log('錯誤信息:', e)
}
}
store.dispatch = exceptionMiddleware
複製代碼
這樣,看起來是把各個加強需求都抽離成單個的函數了,可是都不是純函數,對外部數據有依賴,這樣很不利於擴展。
咱們來把這些加強函數都改爲純函數吧。
咱們分兩步來改造加強函數: (1)把加強函數內調用的加強函數,都抽離出來,做爲參數傳遞進加強函數:
const store = createStore(rootReducer);
const next = store.dispatch;
const loggerMiddleware = (next) => (action) => {
console.log('previous state:', store.getState())
next(action)
console.log('next state:', store.getState())
console.log('action:', action)
}
const updateTimeMiddleware = (next) => (action) => {
console.log('state change time:', new Date().toLocaleString())
next(action)
}
const exceptionMiddleware = (next) => (action) => {
try {
next(action)
} catch (e) {
console.log('錯誤信息:', e)
}
}
store.dispatch = exceptionMiddleware(updateTimeMiddleware(loggerMiddleware(next)))
複製代碼
(2)把加強函數內對store的調用抽離出來,做爲參數傳遞進加強函數:
const store = createStore(rootReducer);
const next = store.dispatch;
const loggerMiddleware = (store) => (next) => (action) => {
console.log('previous state:', store.getState())
next(action)
console.log('next state:', store.getState())
console.log('action:', action)
}
const updateTimeMiddleware = (store) => (next) => (action) => {
console.log('state change time:', new Date().toLocaleString())
next(action)
}
const exceptionMiddleware = (store) => (next) => (action) => {
try {
next(action)
} catch (e) {
console.log('錯誤信息:', e)
}
}
const logger = loggerMiddleware(store)
const updateTime = updateTimeMiddleware(store)
const exception = exceptionMiddleware(store)
store.dispatch = exception(updateTime(logger(next)))
複製代碼
到這裏爲止,咱們真正的實現了可擴展的、獨立的、純的加強函數(加強函數即redux
中間件),而且多個加強函數能夠很方便的組合使用。
咱們上面已經實現了可擴展、獨立的、純的中間件,可是咱們最後還須要執行
const logger = loggerMiddleware(store)
const updateTime = updateTimeMiddleware(store)
const exception = exceptionMiddleware(store)
複製代碼
來生成logger
、updateTime
、exception
, 而且執行
store.dispatch = exception(updateTime(logger(next)))
複製代碼
來覆蓋store
的dispatch
方法。
其實咱們不必關心中間件怎麼使用,咱們只須要知道有三個中間件,剩下的細節都封裝起來。
好比,咱們能夠經過包裝createStore
來把中間件使用細節都封裝在包裝createStore
的代碼內。
指望的用法:
/** * applyMiddleware 接收中間件,把中間件使用細節包裝在了 * applyMiddleware 內部,而後返回一個函數, * 函數接收舊的 createStore,返回新的 createStore */
const newCreateStore = applyMiddleware(exceptionMiddleware, updateTimeMiddleware, loggerMiddleware)(createStore);
// newCreateStore 返回了一個 dispatch 被重寫過的 store
const store = newCreateStore(reducer);
複製代碼
實現applyMiddleware:
const applyMiddleware = (...middlewares) => (oldCreateStore) => (reducer, initState) => {
// 生成 store
let store = oldCreateStore(reducer, initState)
// 給每一個 middleware 傳遞進去 store,至關於 loggerMiddleware(store)
// 爲了防止中間件修改 store 的其餘方法,咱們只暴露 store 的 getState 方法
// 執行結果至關於 chain = [logger, updateTime, exception]
let chain = middlewares.map(middleware => middleware({ getState: store.getState }))
// 獲取 store 的 dispatch 方法
let next = store.dispatch
// 實現 exception(updateTime(logger(next)))
chain.reverse().map(middleware => {
next = middleware(next)
})
// 替換 store 的 dispatch 方法
store.dispatch = next
// 返回新的 store
return store
}
複製代碼
咱們如今包裝createStore
的代碼在createStore
函數外面,爲了使用上的方便,咱們稍微修改下createStore
函數,指望用法:
const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, updateTimeMiddleware, loggerMiddleware)
const store = createStore(reducer, initState, rewriteCreateStoreFunc)
複製代碼
createStore
修改後的代碼以下:
const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
//若是有 rewriteCreateStoreFunc,那就生成新的 createStore
if(rewriteCreateStoreFunc){
const newCreateStore = rewriteCreateStoreFunc(createStore);
return newCreateStore(reducer, initState);
}
// 如下爲舊 createStore 邏輯
let listeners = []
let state = initState || {}
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
}
//注意:Symbol() 不跟任何值相等,因此 type 爲 Symbol(),則 reducer 不匹配任何 type,走 default 邏輯,返回初始化 state 值,這樣就達到了初始化 state 的效果。
dispatch({ type: Symbol() })
return {
getState,
dispatch,
subscribe,
}
}
複製代碼
本小節完整源碼見 demo-2
到這裏,咱們的redux
中間件功能所有實現完了。
store.subscribe
是用來訂閱state
變化方法,既然有訂閱,那麼就存在取消訂閱的需求,咱們來實現下取消訂閱功能。
修改store.subscribe
的實現以下:
const subscribe = (listener) => {
listeners.push(listener)
return () => {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
複製代碼
使用方法以下:
// 訂閱
const unsubscribe = store.subscribe(() => {
let state = store.getState();
console.log(state);
});
// 退訂
unsubscribe();
複製代碼
createStore
容許省略initState
參數,好比這樣:
let store = createStore(rootReducer, rewriteCreateStoreFunc)
複製代碼
修改下咱們的createStore
方法,兼容省略initState
參數的用法:
const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
// 兼容省略參數 initState
if (typeof initState === 'function') {
rewriteCreateStoreFunc = initState
initState = undefined
}
// 若是有 rewriteCreateStoreFunc,那就生成新的 createStore
if(rewriteCreateStoreFunc){
const newCreateStore = rewriteCreateStoreFunc(createStore);
return newCreateStore(reducer, initState);
}
// 如下爲舊 createStore 邏輯
let listeners = []
let state = initState || {}
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
return () => {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
// 注意:Symbol() 不跟任何值相等,因此 type 爲 Symbol(),則 reducer 不匹配任何 type,走 default 邏輯,返回初始化 state 值,這樣就達到了初始化 state 的效果。
dispatch({ type: Symbol() })
return {
getState,
dispatch,
subscribe,
}
}
複製代碼
有時候,咱們的頁面作了按需加載,若是想把每一個按需加載的頁面的組件對應的reducer
也按需加載,能夠經過replaceReducer
來實現,好比:
// 初始頁面須要的 reducer
const reducer = combineReducers({
books: booksReducer
});
const store = createStore(reducer);
// 按需加載的頁面1須要的 reducer,在頁面加載的時候再加載
// 而後,經過 combineReducers 生成新的 reducer
// 最後經過 store.replaceReducer 方法把新的 reducer 替換掉舊的 reducer
const nextReducer = combineReducers({
playfulPerson: playfulPersonReducer,
books: booksReducer,
});
store.replaceReducer(nextReducer);
// 按需加載的頁面2須要的 reducer,在頁面加載的時候再加載
// 而後,經過 combineReducers 生成新的 reducer
// 最後經過 store.replaceReducer 方法把新的 reducer 替換掉舊的 reducer
const nextReducer = combineReducers({
studyPerson: studyPersonReducer,
playfulPerson: playfulPersonReducer,
books: booksReducer,
});
store.replaceReducer(nextReducer);
複製代碼
createStore
函數內部新增replaceReducer
方法,同時返回的store
中包含replaceReducer
方法,代碼實現以下:
const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
// 兼容省略參數 initState
if (typeof initState === 'function') {
rewriteCreateStoreFunc = initState
initState = undefined
}
// 若是有 rewriteCreateStoreFunc,那就生成新的 createStore
if(rewriteCreateStoreFunc){
const newCreateStore = rewriteCreateStoreFunc(createStore);
return newCreateStore(reducer, initState);
}
// 如下爲舊 createStore 邏輯
let listeners = []
let state = initState || {}
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listerner => listerner())
}
const subscribe = (listener) => {
listeners.push(listener)
return () => {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function replaceReducer(nextReducer) {
reducer = nextReducer
// 把新的 reducer 的默認狀態更新到 state 樹上
dispatch({ type: Symbol() })
}
// 注意:Symbol() 不跟任何值相等,因此 type 爲 Symbol(),則 reducer 不匹配任何 type,走 default 邏輯,返回初始化 state 值,這樣就達到了初始化 state 的效果。
dispatch({ type: Symbol() })
return {
getState,
dispatch,
subscribe,
replaceReducer,
}
}
複製代碼
至此,我要實現的一個redux
已經所有實現了,爲了縮短篇幅,省略了用的比較少的bindActionCreators
方法的講解和實現,同時實現代碼中去掉了各類校驗代碼。
完整的示例源碼見 demo-3
文中提到了純函數
,那麼什麼是純函數
呢?
簡單來講,就是相同的輸入,獲得的輸出永遠是同樣的,沒有任何反作用。
純函數
屬於函數式編程中的一個概念。
給你們推薦一本js函數式編程指南的書,支持在線瀏覽函數式編程指南