摘要:在英文中的意思是有效載荷。有一個動作被發射了顧名思義,替換,這主要是方便開發者調試用的。相同的輸入必須返回相同的輸出,而且不能對外產生副作用。怎么辦呢開發者得手動維護一個訂閱器,才能監聽到狀態變化,從而觸發頁面重新渲染。
本文是『horseshoe·Redux專題』系列文章之一,后續會有更多專題推出
來我的 GitHub repo 閱讀完整的專題文章
來我的 個人博客 獲得無與倫比的閱讀體驗
Redux是一套精巧而實用的工具,這也是它在開發者中如此流行的原因。
所以對待Redux,最重要的就是熟練使用它的主要API,一旦將它了然于胸,就會對Redux的設計思想有一個全局的認識,也就能清楚的判斷自己的應用需不需要勞駕Redux出手。
需要注意:咱們默認將Redux和React搭配使用,不過Redux不是非得和它在一起的。
Action要達成某個目的,開發者首先要描述自己的意圖。Action就是用來描述開發者意圖的。它不是一個函數,而是一個普通的對象,通過聲明的類型來觸發相應的動作。
我們來看一個例子:
{ type: "ADD_TODO_ITEM", payload: { content: "每周看一本書", done: false, }, }
Redux官方定義了字段的一些規范:一個Action必須包含type字段,同時一個Action包含的字段不應該超過type、payload、error、meta這四種。
type聲明Action的類型。一般用全大寫的字符串表示,多個字母用下劃線分隔。
payload在英文中的意思是有效載荷。引申到程序中就是有效字段的意思,也就是說真正用于構建應用的信息都應該放到payload字段里。
error字段并不承載錯誤信息,而是一個出錯的token。只有當值為true時才表示出錯,值為其他或者干脆沒有該字段表示程序運行正常。那么錯誤信息放哪呢?當然是放payload里面,因為錯誤信息也屬于構建應用的有效信息。
meta在英文中的意思是元。在這里表示除了payload之外的信息。
因為意圖是通過類型來定義的,所以type字段必不可少,稱某個對象為一個Action的標志就是它有一個type字段。
除此之外,一個動作可能包含更為豐富的信息。開發者可以隨意添加字段,畢竟它就是個普通對象。不過遵守一定的規范便于其他開發者閱讀你的代碼,可以提升協作效率。
Constants前面說了type字段一般用全大寫的字符串表示,多個字母用下劃線分隔。不僅如此,大家還有一個約定俗成:用一個結構相同的變量保存該字符串,因為它會在多處用到。
const ADD_TODO_ITEM = "ADD_TODO_ITEM";
集中保存這些變量的文件就叫Constants.js。
在此,我提出一點異議。如果你覺得不麻煩,那遵循規范再好不過。但開發者向來覺得Redux過于繁瑣,如果你也這么覺得,大可不必維護所謂的Constants。維護Constants的好處不過是一處更改處處生效,然而字符串和變量是結構相同的,如果字符串作了修改,語意上必然大打折扣,況且type字段一旦定義極少更改,所以視你的協作規模和個人喜好而定,為Redux的繁瑣減負不是么?
Action Creators我們知道Action是一個對象,但是如果多次用到這個對象,我們可以寫一個生成Action的函數。
function addTodoItem(content) { return { type: ADD_TODO_ITEM, payload: { content, done: false }, }; }
同理,如果你覺得繁瑣,這一步是可以免去的。
異步場景下Action Creators會大有用處,后面會講到。
需要注意的是:所謂的Action更確切的說是一個執行動作的指令,而不是一個動作?;蛘呶覀儞Q一種說法,這里的動作指的是動作描述,而不是動作派發。
StoreRedux的本質不復雜,就是用一個全局的外部的對象來存儲狀態,然后通過觀察者模式來構建一套狀態更新觸發通知的機制。
這里的Store就是存儲狀態的容器。
但是呢?它需要開發者動手寫一套邏輯來指導它怎么處理狀態的更新,這就是后面要講的Reducer,暫且按下不表。
問題是Store怎么接收這套邏輯呢?
import { createStore } from "redux"; import reducer from "./reducer"; const store = createStore(reducer);
看到沒有,Redux專門有一個API用來創建Store,它接受三個參數:reducer、preloadedState和enhancer。
reducer就是處理狀態更新的邏輯。
preloadedState是初始狀態,如果你需要讓Store一開始不是空對象,那么可以從這里傳進去。
enhancer翻譯成中文是增強器,是用來裝載第三方插件以增強Redux的功能的。
怎么存我們已經了解了Action的作用,但是Action只是對動作的描述,怎么說它得有個發射器吧。這個發射器就隱藏在Store里。
執行createStore返回的對象包含了一個函數dispatch,傳入Action執行就會發射一個動作。
import React, { Component } from "react"; import store from "./store"; import action from "./action"; class App extends Component { render() { return ( ); } } export default App;怎么取
好了我們已經發射了一個動作,假設現在Store中已經有狀態了,我們怎么把它取出來呢?
直接store.xxx么?
我們先來打印Store這個對象看看:
{ dispatch: ? dispatch(action), getState: ? getState(), replaceReducer: ? replaceReducer(nextReducer), subscribe: ? subscribe(listener), Symbol(observable): ? observable(), }
打印出來一堆API,這可咋整?
別著急,茫茫人海中看到一個叫getState的東西,它就是我們要找的高人吧。插一句,大家注意區分Store和State的區別,Store是存儲State的容器。
Redux隱藏了Store的內部細節,所以開發者只能用getState來獲取狀態。
訂閱Redux是基于觀察者模式的,所以它開放了一個訂閱的API給開發者,每次發射一個動作,傳入訂閱器的回調都會執行。通過它開發者就能監聽動作的派發以執行相應的邏輯。
import store from "./store"; store.subscribe(() => console.log("有一個動作被發射了"));replaceReducer
顧名思義,替換Reducer,這主要是方便開發者調試Redux用的。
ReducerReducer是Redux的核心概念,因為Redux的作者Dan Abramov這樣解釋Redux這個名字的由來:Reducer+Flux。
其實Reducer是一個計算機術語,包括JavaScript中也有用于迭代的reduce函數。所以我們先來聊聊應該怎樣理解Reducer這個概念。
reduce翻譯成中文是減少,Reducer在計算機中的含義是歸并,也是化多為少的意思。
我們來看JavaScript中reduce的寫法:
const array = [1, 2, 3, 4, 5]; const sum = array.reduce((total, num) => total + num);
再來看Redux中Reducer的寫法:
function todoReducer(state = [], action) { switch (action.type) { case "ADD_TODO_ITEM": const { content, done } = action.payload; return [...state, { content, done }]; case "REMOVE_TODO_ITEM": const todos = state.filter(todo => todo.content !== action.content); return todos; default: return state; } }
state參數是一個舊數據集合,action中包含的payload是一個新的數據項,Reducer要做的就是將新的數據項和舊數據集合歸并到一起,返回給Store。這樣看起來Reducer這個名字起的也沒那么晦澀了是不是?
一個Reducer接受兩個參數,第一個參數是舊的state,我們返回的數據就是用來替換它的,然后風水輪流轉,這次返回的數據下次就變成舊的state了,如此往復;第二個參數是我們派發的action。
因為Reducer的結構類似,都是根據Action的類型返回相應的數據,所以一般采用switch case語句,如果沒有變動則返回舊的state,總之它必須有返回值。
純函數Reducer的作用是歸并,也只能是歸并,所以Redux規定它必須是一個純函數。相同的輸入必須返回相同的輸出,而且不能對外產生副作用。
所以開發者在返回數據的時候不能直接修改原有的state,而是應該在拷貝的副本之上再做修改。
多個Reducer一個Reducer只應該處理一個動作,可是我們的應用不可能只有一個動作,所以一個典型的Redux應用會有很多Reducer函數。那么怎么管理這些Reducer呢?
首先來看只有一個Reducer的情況:
import { createStore } from "redux"; import reducer from "./reducer"; const store = createStore(reducer); export default store;
如果只有一個Reducer,那我們只需要將它傳入createStore這個函數中,就這么簡單。這時候Reducer返回的狀態就是Store中的全部狀態。
而如果有多個Reducer,我們就要動用Redux的另一個API了:combineReducers。
const reducers = combineReducers({ userStore: userReducer, todoStore: todoReducer, });
當我們有多個Reducer,就意味著有多個狀態需要交給Store管理,我們就需要子容器來存儲它們,其實就是對象嵌套對象的意思。combineReducers就是用來干這個的,它把每一個Reducer分門別類的與不同的子容器對應起來,某個Reducer只處理對應的狀態。
{ userStore: {}, todoStore: {}, };
當我們用getState獲取整個Store的狀態,返回的對象就是上面這樣的。
你猜對了,傳入combineReducers的對象的key就是子容器的名字。
默認值當開發者調用createStore創建Store時,傳入的所有Reducer都會執行一遍。注意,這時開發者還沒有發射任何動作呢,那為什么會執行一遍?
const randomString = () => Math.random().toString(36).substring(7).split("").join("."); const ActionTypes = { INIT: `@@redux/INIT${randomString()}`, REPLACE: `@@redux/REPLACE${randomString()}`, PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}` }; dispatch({ type: ActionTypes.INIT });
因為Redux源碼中,在createStore函數里面放了這樣一段邏輯,這初始化時的dispatch是Redux自己發射的。
為什么?
還記得Reducer接受兩個參數嗎?第一個是state,而我們可以給state設置默認值。
聰明的你一定想到了,初始化Store時Redux自己發射一個動作的目的是為了收集這些默認值。Reducer會將這些默認值返回給Store,這樣默認值就保存到Store中了。
聰明的你大概還想到一個問題:createStore也有默認值,Reducer也有默認值,不會打架么?
Redux的規矩:createStore的默認值優先級更高,所以不會打架。
執行在一個有若干Reducer的應用中,一個動作是怎么找到對應的Reducer的?
這是一個好問題,答案是挨個找。
假如應用有1000個Reducer,與某個動作對應的Reducer又恰好在最后一個,那要把1000個Reducer都執行一遍,Redux不會這么傻吧?
Redux還真就這么傻。
因為當一個動作被派發時,Redux并不知道應該由哪個Reducer來處理,所以只能讓每個Reducer都處理一遍,看看到底是誰的菜。可不可以在設計上將動作與Reducer對應起來呢?當然是可以的,但是Redux為了保證API的簡潔和優美,決定犧牲這一部分性能。
只是一些純函數而已,莫慌。
react-redux當我們使用Redux時,我們希望每發射一個動作,應用的狀態自動發生改變,從而觸發頁面的重新渲染。
import React, { Component } from "react"; import store from "./store"; class App extends Component { state = { name: "Redux" }; render() { const { name } = this.state; return ({name}); } componentDidMount() { this.unsubscribe = store.subscribe(() => { const { name } = store.getState(); this.setState({ name }); }); } componentWillUnmount() { this.unsubscribe(); } }
怎么辦呢?開發者得手動維護一個訂閱器,才能監聽到狀態變化,從而觸發頁面重新渲染。
但是React最佳實踐告訴我們,一個負責渲染UI的組件不應該有太多的邏輯,那么有沒有更好的辦法使得開發者可以少寫一點邏輯,同時讓組件更加優雅呢?
別擔心,Redux早就幫開發者做好了,不過它是一個獨立的模塊:react-redux。顧名思義,這個模塊的作用是連接React和Redux。
Provider連接React和Redux的第一步是什么呢?當然是將Store集成到React組件中,這樣我們就不用每次在組件代碼中import store了。多虧了React context的存在,Redux只需要將Store傳入根組件,所有子組件就能通過某種方式獲取傳入的Store。
import React from "react"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import store from "./store"; import App from "./App"; ReactDOM.render(connect, document.getElementById("root") );
老式的context寫法,在子組件中定義contextTypes就可以接收到傳入的參數。當然,你肯定也想到,Redux把這些細節都封裝好了,這就是connect。
connect接口的意義主要有三點:
封裝用context從根組件獲取數據的細節。
封裝Redux訂閱器的細節。
作為一個容器組件真正連接React和Redux。
import React from "react"; import Todo from "./Todo"; const App = ({ todos, addTodoItem }) => { return ({todos.map(todo =>); } const mapStateToProps = (state, ownProps) => { return { todos: state.todoStore, }; }; const mapDispatchToProps = (dispatch, ownProps) => { return { addTodoItem: (todoItem) => dispatch({ type: "ADD_TODO_ITEM", payload: todoItem }), }; }; export default connect(mapStateToProps, mapDispatchToProps)(App);)}
我們看上面例子,connect接受的兩個參數:mapStateToProps和mapDispatchToProps,所謂的map就是映射,意思就是將所有state和dispatch依次映射到props上。如此真正的組件需要的數據和功能都在props上,它就可以安安心心的做一個傻瓜組件。
connect接受四個參數:
mapStateToProps。也可以寫成mapState,這個參數是用來接收訂閱得到的數據更新的,也就是說如果這個參數傳null或者undefined,則被connect包裹的組件無法收到更新的數據。mapStateToProps必須是一個函數,而且必須返回一個純對象。它接收兩個參數,第一個參數是存儲在Store中完整的state,第二個參數是被connect包裹的組件自身的屬性。假如App組件掛載時寫成這樣:
mapDispatchToProps。也可以寫成mapDispatch,這個參數是用來封裝所有發射器的。mapDispatchToProps必須是一個函數,而且必須返回一個純對象。它接收兩個參數,第一個參數是dispatch發射器函數,第二個參數和mapStateToProps的第二個參數相同。
mergeProps。顧名思義,合并props?,F在被connect包裹的組件擁有三種props:由state轉化而來的props,由dispatch轉化而來的props,自身的props。它返回的純對象就是最終組件能接收到的props。默認返回的對象是用Object.assign()合并上述三種props。
options。用來自定義connect的選項。
我們注意到,connect要先執行一次,返回的結果再次執行才傳入開發者定義的組件。它返回一個新的組件,這個新的組件不會修改原組件(除非你操縱了ownProps的返回),而是為組件增加一些新的props。
我們也可以用裝飾器寫法來重寫connect:
import React from "react"; import Todo from "./Todo"; const mapStateToProps = (state, ownProps) => { return { todos: state.todoStore, }; }; const mapDispatchToProps = (dispatch, ownProps) => { return { addTodoItem: (todoItem) => dispatch({ type: "ADD_TODO_ITEM", payload: todoItem }), }; }; @connect(mapStateToProps, mapDispatchToProps) const App = ({ todos, addTodoItem }) => { return (總結{todos.map(todo =>); } export default App;)}
Redux通過調用createStore返回Store,它是一個獨立于應用的全局對象,通過觀察者模式能讓應用監聽到Store中狀態的變化。最佳實踐是一個應用只有一個Store。
Redux必須通過一個明確的動作來修改Store中的狀態,描述動作的是一個純對象,必須有type字段,傳遞動作的是Store的屬性方法dispatch。
Store本身并沒有任何處理狀態更新的邏輯,所有邏輯都要通過Reducer傳遞進來,Reducer必須是一個純函數,沒有任何副作用。如果有多個Reducer,則需要利用combineReducers定義相應的子狀態容器。
基于容器組件和展示組件分離的設計原則,也為了提高開發者的編程效率,Redux通過一個額外的模塊將React和Redux連接起來,使得所有的狀態管理接口都映射到組件的props上。其中,Provider將Store注入應用的根組件,解決的是連接的充分條件;connect將需要用到的state和dispatch都映射到組件的props上,解決的是連接的必要條件。只有被Provider包裹的組件,才能使用connect包裹。
Redux專題一覽考古
實用
中間件
時間旅行
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97919.html
摘要:光憑一個是無法實現血緣關系疏遠的組件之間的狀態同步的。就是為解決這個問題而生的。,處理動作的派發,相當于架構的。我們的主角是,它也是目前社區最受歡迎的狀態管理框架。專題一覽考古實用中間件時間旅行 本文是『horseshoe·Redux專題』系列文章之一,后續會有更多專題推出來我的 GitHub repo 閱讀完整的專題文章來我的 個人博客 獲得無與倫比的閱讀體驗 React的橫空出世給...
摘要:好處就是不再需要能夠處理異步的中間件了。不過,它是一個研究中間件很好的范本。執行它,返回的是由第二層函數組成的中間件數組。也就是說呀同學們,除了最后一個中間件的是原始的之外,倒數往前的中間件傳入的都是上一個中間件的邏輯函數。 本文是『horseshoe·Redux專題』系列文章之一,后續會有更多專題推出來我的 GitHub repo 閱讀完整的專題文章來我的 個人博客 獲得無與倫比的閱...
平日學習接觸過的網站積累,以每月的形式發布。2017年以前看這個網址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進擊的 Promise Effective JavaScript leeheys blog -...
平日學習接觸過的網站積累,以每月的形式發布。2017年以前看這個網址:http://www.kancloud.cn/jsfron... 03月份前端資源分享 1. Javascript 175453545 Redux compose and middleware 源碼分析 深入 Promise(二)——進擊的 Promise Effective JavaScript leeheys blog -...
閱讀 2457·2019-08-30 15:53
閱讀 2572·2019-08-29 13:11
閱讀 2653·2019-08-29 12:45
閱讀 3486·2019-08-29 12:41
閱讀 2326·2019-08-26 10:14
閱讀 2154·2019-08-23 14:39
閱讀 2314·2019-08-23 12:38
閱讀 3378·2019-08-23 12:04