衆所周知,由於React自己只是View層的框架,對於總體業務架構來講是有缺失的,因此咱們常常會在React應用中接入Flux、Redux等架構模式,固然也能夠選擇使用Mobx(相似Vuex)等集成工具。javascript
就拿使用較廣的Redux架構來講,在React中實現後,每每須要將store中的數據掛載到組件的狀態中,當subscribe到state改變後再調用setState來更新組件對應的狀態來實現數據同步。好的,問題來了,使用Hook後組件內部該怎麼處理呢?其實很簡單,只須要利用useState來構建狀態就行了。vue
const Example = () => {
const [count, setCount] = useState(store.getState().count)
store.subscribe(() => {
setCount(store.getState().count)
})
return (<div>{count}</div>)
}
複製代碼
是否是很簡單呢?固然了,咱們通常會使用react-redux來簡化redux的使用,使用來Hook後,對應的工具庫固然也要作更換,增長一大波學習成本。java
咱們要實現的Todolist實例組件間其實有很強的聯動性,因此必然要將一些數據進行集中的管理。react
其實React 提供了不少Hook工具,不僅有咱們看到的useState,其餘的咱們慢慢來學習,咱們先來學習一下useReducer的使用,這個東西就能夠幫助咱們構建一個簡版的Store,雖說是簡陋來一些,可是咱們構建的合理一些其實也能知足應用的需求。vuex
const [state, dispatch] = useReducer(reducer, initialArg, init);
複製代碼
useReducer實際上是useState的升級版本,能夠利用一個(state, action) => newState
的 reducer來構建數據,而且還能夠生成與之匹配的dispatch方法,你們看到這些就忽然發現,這個玩意和redux很像對吧,哈哈,ok,那咱們再來看一下這個東西怎麼用。redux
// 初始狀態
const initialState = {count: 0};
// reducer純函數,接收當前的state和action,action身上帶有標示性的type
// reducer每次執行都會返回一個新的數據
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
// 利用useReducer生成狀態
// 第一個參數爲處理數據的reducer,第二個參數爲初始狀態
// dispatch每次調用都會觸發reducer來生成新的狀態,具體的處理由傳入的action決定
const [state, dispatch] = useReducer(reducer, initialState);
return (
<> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 複製代碼
useReducer能夠接收三個參數。第一個參數爲處理數據的reducer,第二個參數爲初始狀態,第三個參數能夠不傳,它的用處是惰性處理初始狀態,例如:數組
// 初始狀態
const initialState = 0
function reducer(state, action) {
// ...
}
function init (state) {
return { count: state }
}
function Counter() {
// 雖然咱們傳入的初始狀態是 0 ,可是最終的初始化狀態爲: init(initialState)
// 因此最終初始狀態爲{ count: 0 }
// 其實用處不大...脫褲子放屁的感受....
const [state, dispatch] = useReducer(reducer, initialState, init);
return (
// ...
);
}
複製代碼
ok,這不state也有了,reducer也有了,根據dispatch我們再整個actionCreator 出來,小小的Redux架構不就搭建完成了麼?架構
在動手以前,咱們想到一個問題,useReducer是要在函數組件裏面使用的呀,咱們不能在每個要使用state的組件中都利用useReducer構建一個store吧?那樣不就各自爲政了嘛,還談什麼狀態集中管理,那怎麼辦呢?框架
答案就是:整出一個你們的爸爸(最外層的父組件)來,而後套在外面,由這個父組件來管理這些狀態,而後再將狀態和action方法給內部的子組件傳入。ide
可是,這樣依然存在一個問題待解決:一層一層的傳數據太麻煩了,不科學。那該怎麼解決呢?
沒錯,聰明的小夥伴已經想到了,咱們用context來解決數據的傳遞問題,那麼利用context傳遞有什麼很差的地方嗎?
答案是沒啥事兒,react-redux是怎麼讓全部的容器組件都能獲取到store中的狀態再傳遞給UI組件的呢,還不是在最外面有個Provider利用context樹給它們提供了store嘛!
ok,一切問題都解決了,準備開始!
構建store的方法其實很簡單,可是爲告終構分離順便再多搞一個知識點,咱們準備利用一個自定義Hook來完成store的構建。
自定義Hook的目的是將一些邏輯提高到某個可重用的函數中,相似於HOC的存在同樣,自定義的Hook須要有這樣的條件:
OK,內心有數了,咱們先把其餘的東西構建出來(actions, initialState):
默認狀態,我們利用immutable來構建不可變狀態,優化性能:
import { is, fromJS } from 'immutable'
// fromJS能夠將一個普通結構的數據生成爲immutable結構的數據
const initialState = fromJS({
items: []
})
複製代碼
reducer,在內部已經實現了關於Todolist業務的一些處理,咱們準備將todolist數據存放在localStorage中,爲了操做方便使用localstorage包:
import LocalStorage from 'localstorage'
// 這個東東能夠方便的操做localStorage
const TodoList_LS = new LocalStorage('todolist_')
// reducer接受當前的狀態(設置默認狀態)以及action
// action中包含這次動做的type標示與payload載體
const reducer = (state = initialState, { type, payload }) => {
// 準備返回的狀態
let result = state
switch (type) {
// 更新所有items
case 'UPDATE_ITEMS':
// immutable基本操做,設置items後返回的就是一個新的狀態
// 此時result !== state 喲
result = state.set('items', fromJS(payload.items))
break
// 新建某個item
case 'CREATE_ITEM':
result = state.set('items', state.get('items').push(fromJS(payload.item)))
break
// 完成某個item
case 'FINISH_ITEM':
result = state.set('items', state.get('items').update(
state.get('items').findIndex(function(item) {
return is(item.get('id'), payload.id)
}), function(item) {
return item.set('finished', !item.get('finished'))
})
)
break
// 更新item的title和description
case 'UPDATE_ITEM':
result = state.set('items', state.get('items').update(
state.get('items').findIndex(function(item) {
return is(item.get('id'), payload.item.id)
}), function(item) {
item = item.set('title', payload.item.title)
item = item.set('description', payload.item.description)
return item
})
)
break
// 刪除某個item
case 'DELETE_ITEM':
let list = state.get('items')
let index = list.findIndex((item) => is(item.get('id'), payload.id))
result = state.set('items', list.remove(index))
break
default: break
}
// 將更新後的items存入localStorage中
TodoList_LS.put('items', result.get('items').toJS())
return result
}
複製代碼
在這裏簡單說一下immutable的使用,當數據轉換爲immutable數據後,利用對應的set、get、update等APi操做數據後都能返回一個新的數據。
你們能夠看到reducer的操做基本與redux的reducer構建方式同樣,內部包含的也僅僅是一些增刪改差的簡單操做。
接下來咱們再來創造一個actions工具,內含不少方法,每一個方法均可以調用dispatch來觸發reducer的執行並傳入對應的action(包含標識的type和數據載體payload)。
const actions = {
getInitialItems () {
let [err, items] = TodoList_LS.get('items')
if (err) items = []
this.dispatch({
type: 'UPDATE_ITEMS',
payload: { items }
})
},
createTodoItem ({ item }) {
let [err, id] = TodoList_LS.get('id')
if (err) id = 0
item.id = ++id
item.finished = false
this.dispatch({
type: 'CREATE_ITEM',
payload: { item }
})
TodoList_LS.put('id', item.id)
},
finishTodo ({ id }) {
this.dispatch({
type: 'FINISH_ITEM',
payload: { id }
})
},
deleteTodo ({ id }) {
this.dispatch({
type: 'DELETE_ITEM',
payload: { id }
})
},
updateTodoItem ({ item }) {
this.dispatch({
type: 'UPDATE_ITEM',
payload: { item }
})
}
}
複製代碼
你們能夠看到在actions的方法中都在調用一個this.dispatch方法,這個方法是哪來的呢,咱們一下子就把useReducer生成出來的reducer掛載到actions身上不就有了麼。
最後輪到咱們的自定義Hook了,甩出來瞅瞅:
// 構建Store的Custom Hook
const StoreHook = ( ) => {
// 利用useReducer構建state與dispatch
let [ state, dispatch ] = useReducer(reducer, initialState)
// 爲actions掛載dispatch,防止更新的時候掛載屢次
if (!actions.dispatch) actions.dispatch = dispatch
// immutable數據轉換爲普通結構數據
let _state = state.toJS()
// Hook生成的數據
let result = [
_state,
actions
]
return result
}
複製代碼
咱們構建的自定義Hook-StoreHook在實例中沒有複用的場景,在這裏僅僅是爲了分離Store的構建以及學習自定義Hook。
在StoreHook中利用useReucer生成了state和dispatch方法,將dispatch方法掛載在actions身上以便actions內部的方法來調用觸發reducer,將生成的狀態及actions返回出去,這樣使用StoreHook的組件就能夠獲得咱們構建好準備集中管理的state和actions了。
let [state, actions] = StoreHook()
複製代碼
上面咱們以及討論過了,只要使用咱們自定義的StoreHook,就能夠獲得state和actions,可是整個實例只能集中的管理一個state,因此咱們不能在多個組件中同時使用StoreHook,因此咱們須要構建一個專門用來構建state和actions並將其傳遞給全部子組件的「你們的爸爸」組件。
export const StoreContext = React.createContext({})
export const HookStoreProvider = (props) => {
let [state, actions] = StoreHook()
return (
<StoreContext.Provider value = {{ state, actions }}> { props.children } </StoreContext.Provider> ) } 複製代碼
你們能夠看到HookStoreProvider組件在構建了context將state和actions進行傳遞,很是棒,把它包在組件結構的最外面吧。
import { HookStoreProvider } from '@/hooks/todolist'
class App extends Component {
render () {
return (
<HookStoreProvider> <TodoList/> </HookStoreProvider>
)
}
}
export default App
複製代碼
ok,那麼咱們的組件須要怎麼去使用HookStoreProvider在context樹上傳遞的狀態和組件呢?傳統的context使用方法倒也能夠:
import { StoreContext } from './store.js'
const TodoListContent = () => {
return (
<StoreContext.Consumer> { (value) => (<div>{ value }</div>) } </StoreContext.Consumer> ) } 複製代碼
這樣的使用方式有點麻煩,幸虧React Hook提供來useContext的Hook工具,這就簡單多了:
let values = useContext(StoreContext)
let { state, actions } = values
複製代碼
useContext傳入Context對象後能夠返回此Context對象掛載在context樹上的數據,這就不須要什麼Consumer了,簡單粗暴,那麼咱們在大膽一點,爲了讓想要使用狀態和actions的組件連useContext都不用寫,並且還能夠經過傳個getter參數就能去到state中的某個狀態的衍生狀態,(相似於vuex中的getters),這樣的「中間商」正好能夠再利用Custom Hook(自定義Hook)來構建:
// 關於state的衍生狀態
const getters = {
// 關於todos的展現信息數據
todoDetail: (state) => {
let items = state.items
let todoFinishedItems = items.filter(item => item.finished)
let todoUnfinishedItems = items.filter(item => !item.finished)
let description = `當前共有 ${items.length} 條待辦事項,其中${todoFinishedItems.length}條已完成,${todoUnfinishedItems.length}條待完成。`
// 返回描述、已完成、未完成、所有的items
return {
description,
todoItems: items,
todoFinishedItems,
todoUnfinishedItems
}
},
// 返回全部的items
items: (state) => {
return state.items
}
}
// 自定義Hook,接受context中的狀態並根據傳入的getter來獲取對應的getter衍生數據
export const useTodolistStoreContext = ( getter ) => {
let {state, actions} = useContext(StoreContext)
let result = [
getter ? getters[getter](state) : state,
actions
]
return result
}
複製代碼
上面的代碼構建了getters工具和useTodolistStoreContext自定義Hook,這樣只要在組件內使用這個Hook就能夠獲得state或者衍生的getters以及actions了。
const TodoListContent = (props) => {
let [items] = useTodolistStoreContext('items')
return (
<div className="todolist-content">{ items }</div>
)
}
複製代碼
這樣咱們的關於Store的構建也以及完成了,在這裏咱們研究了useReducer、useContext以及自定義Hook的使用了。後面的內容中,咱們將在實例的繼續構建中學習useState,useEffect,useRef的使用。