摘要:二基礎就是一個普通的。其他屬性用來傳遞此次操作所需傳遞的數據,對此不作限制,但是在設計時可以參照標準。對于異步操作則將其放到了這個步驟為添加一個變化監聽器,每當的時候就會執行,你可以在回調函數中使用來得到當前的。
注:這篇是16年10月的文章,搬運自本人 blog...
https://github.com/BuptStEve/...
參考資料
英文原版文檔
中文文檔
墻裂推薦作者出的教學視頻 基礎篇
墻裂推薦作者出的教學視頻 高級篇
首先要明確一點,雖然 redux 是由 flux 演變而來,但我們完全可以并且也應該拋開 react 進行學習,這樣可以避免一開始就陷入各種細節之中。
所以推薦使用 jsbin 進行調試學習,或者使用 create-react-app 作為項目腳手架。
一、Redux 是什么?Redux is a predictable state container for JavaScript apps.
Redux 是一個 JavaScript 狀態容器,提供可預測化的狀態管理。
先不要在意那些細節
總的來說,redux 使用 store 保存并管理頁面中的各種狀態(state)
當需要改變 state 時,使用 dispatch 調用 action creators 觸發 action
接著使用純函數(pure function)reducer 來處理這些 action,它會根據當前 state 和 action 返回(注意這里不是修改)新的 state
view 層可以對于 state 進行訂閱(subscribe),這樣就可以得到新的 state,從而可以刷新界面(所以十分適合數據驅動的前端框架)
純函數:簡單的說就是對于同樣的輸入總是返回同樣的輸出,并且沒有副作用的函數。(推薦學習了解下函數式編程)1.1. 為什么選擇 redux?
隨著 JavaScript 單頁應用開發日趨復雜,JavaScript 需要管理比任何時候都要多的 state (狀態)。 這些 state 可能包括服務器響應、緩存數據、本地生成尚未持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標簽,是否顯示加載動效或者分頁器等等。
管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那么當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發生了什么。state 在什么時候,由于什么原因,如何變化已然不受控制。 當系統變得錯綜復雜的時候,想重現問題或者添加新功能就會變得舉步維艱。
如果這還不夠糟糕,考慮一些來自前端開發領域的新需求,如更新調優、服務端渲染、路由跳轉前請求數據等等。前端開發者正在經受前所未有的復雜性,難道就這么放棄了嗎?當然不是。
這里的復雜性很大程度上來自于:我們總是將兩個難以厘清的概念混淆在一起:變化和異步。 我稱它們為曼妥思和可樂。如果把二者分開,能做的很好,但混到一起,就變得一團糟。一些庫如 React 試圖在視圖層禁止異步和直接操作 DOM 來解決這個問題。美中不足的是,React 依舊把處理 state 中數據的問題留給了你。Redux就是為了幫你解決這個問題。
跟隨 Flux、CQRS 和 Event Sourcing 的腳步,通過限制更新發生的時間和方式,Redux 試圖讓 state 的變化變得可預測。這些限制條件反映在 Redux 的 三大原則中。
簡單總結就是使用 Redux 我們就可以沒有蛀牙(大霧)
擁有可預測(predictable)的應用狀態,所以應用的行為也是可預測的
因為 reducer 是純函數,所以方便對于狀態遷移進行自動化測試
方便地記錄日志,甚至實現時間旅行(time travel)
1.2. 三大原則(哲♂學) 1.2.1. 單一數據源(Single source of truth)整個應用的 state 被儲存在一棵 object tree 中,并且這個 object tree 只存在于唯一一個 store 中。
來自服務端的 state 可以在無需編寫更多代碼的情況下被序列化并注入到客戶端中
便于調試,在開發時可以將狀態保存在本地
Undo/Redo 可以輕松實現,從而實現時間旅行
1.2.2. State 是只讀的(State is read-only)惟一改變 state 的方法就是觸發 action,action 是一個用于描述已發生事件的普通對象。
因為所有的修改都被集中化處理,且嚴格按照一個接一個的順序執行,(dispatch 同步調用 reduce 函數)因此不用擔心 race condition 的出現。 Action 就是普通對象而已,因此它們可以被日志打印、序列化、儲存、后期調試或測試時回放出來。
1.2.3. 使用純函數來執行修改(Changes are made with pure functions)為了描述 action 如何改變 state tree ,你需要編寫 reducer。
Reducer 只是純函數,它接收先前的 state 和 action,并返回新的 state。剛開始你可以只有一個 reducer,隨著應用變大,你可以把它拆成多個小的 reducers,分別獨立地操作 state tree 的不同部分。
二、Redux 基礎 2.1. actionAction 就是一個普通的 JavaScript Object。
redux 唯一限制的一點是必須有一個 type 屬性用來表示執行哪種操作,值最好用字符串,而不是 Symbols,因為字符串是可被序列化的。
其他屬性用來傳遞此次操作所需傳遞的數據,redux 對此不作限制,但是在設計時可以參照 Flux 標準 Action。
簡單總結 Flux Standard action 就是
2.2. reducer一個 action 必須是一個 JavaScript Object,并且有一個 type 屬性。
一個 action 可以有 payload/error/meta 屬性。
一個 action 不能有其他屬性。
Reducer 的工作就是接收舊的 state 和 action,返回新的 state。
(previousState, action) => newState
之所以稱作 reducer 是因為它將被傳遞給 Array.prototype.reduce(reducer, ?initialValue) 方法。保持 reducer 純凈非常重要。永遠不要在 reducer 里做這些操作:
修改傳入參數;
執行有副作用的操作,如 API 請求和路由跳轉;
調用非純函數,如 Date.now() 或 Math.random()。
2.3. storeStore 就是用來維持應用所有的 state 樹的一個對象。
在 redux 中只有一個 store(區別于 flux 的多個 store),在 store 中保存所有的 state,可以把它當成一個封裝了 state 的類。而除了對其 dispatch 一個 action 以外無法改變內部的 state。
在實際操作中我們只需要把根部的 reducer 函數傳遞給 createStore 就可以得到一個 store。
import { createStore } from "redux"; function reducer(state, action) { switch (action.type) { case "SOME_ACTION": // 一些操作 return newState; // 返回新狀態 default: return state; } } const store = createStore(reducer);
redux 中提供了這幾個 api 操作 store
2.3.1. getState返回當前的整個 state 樹。
2.3.2. dispatch(action)分發 action 給對應的 reducer。
該函數會調用 getState() 和傳入的 action 以【同步】的方式調用 store 的 reduce 函數,然后返回新的 state。從而 state 得到了更新,并且變化監聽器(change listener)會被觸發。(對于異步操作則將其放到了 action creator 這個步驟)
2.3.3. subscribe(listener)為 store 添加一個變化監聽器,每當 dispatch 的時候就會執行,你可以在 listener(回調函數)中使用 getState() 來得到當前的 state。
這個 api 設計的挺有意思,它會返回一個函數,而你執行這個函數后就可以取消訂閱。
2.3.4. replaceReducer(nextReducer)替換 store 當前用來計算 state 的 reducer。
這是一個高級 API。只有在你需要實現代碼分隔,而且需要立即加載一些 reducer 的時候才可能會用到它。在實現 Redux 熱加載機制的時候也可能會用到。
2.4. createStore忽略各種類型判斷,實現一個最簡的 createStore 可以用以下代碼。參考資料
const createStore = (reducer) => { let state; let listeners = []; const getState = () => state; const dispatch = (action) => { state = reducer(state, action); // 調用 reducer listeners.forEach(listener => listener()); // 調用所有變化監聽器 }; const subscribe = (listener) => { listeners.push(listener); return () => { // 返回解除監聽函數 listeners = listeners.filter(l => l !== listener); }; } dispatch({}); // 初始化 return { getState, dispatch, subscribe }; };2.5. 計數器例子
純 JavaScript 不涉及界面(可以在右側 console 中嘗試 store.dispatch)
{% iframe http://jsbin.com/kejezih/edit... 100% 600 %}
增加界面
{% iframe http://jsbin.com/jihara/edit?... 100% 600 %}
三、與 React 進行結合 3.1. 通過 script 標簽導入 react實現同樣功能的 Counter
{% iframe http://jsbin.com/qalevu/edit?... 100% 800 %}
3.2. 用 Redux 和 React 實現 TodoApp在添加 react-redux 之前,為了體會下 react-redux 的作用,首先來實現一個比計數器更復雜一點兒的 TodoApp 栗子~
3.2.1. 分析與設計組件一般分為
容器組件(Smart/Container Components)
展示組件(Dumb/Presentational Components)
- | 容器組件 | 展示組件 |
---|---|---|
Location | 最頂層,路由處理 | 中間和子組件 |
Aware of Redux | 是 | 否 |
讀取數據 | 從 Redux 獲取 state | 從 props 獲取數據 |
修改數據 | 向 Redux 派發 actions | 從 props 調用回調函數 |
最佳實踐一般是由容器組件負責一些數據的獲取,進行 dispatch 等操作。而展示組件組件不應該關心邏輯,所有數據都通過 props 傳入。
這樣才能達到展示組件可以在多處復用,在具體復用時就是通過容器組件將其包裝,為其提供所需的各種數據。
一個 TodoApp 包含了三個部分:
頂部的 AddTodo 輸入部分
中間的 TodoList 展示部分
底部的 Footer 過濾部分
State 應該包含:
filter:過濾 todos 的條件
SHOW_ALL
SHOW_ACTIVE
SHOW_COMPLETED
todos:所有的 todo
todo:包含 id、text 和 completed
然而傳到應用中的 props 只需要:
visibleTodos:過濾后的 todos
filter:過濾條件
Action 應該有三種:
ADD_TODO
TOGGLE_TODO
SET_VISIBILITY_FILTER
3.2.2. 編碼實現// 暫且使用數字作為 id let nextTodoId = 0; /*-- action creators --*/ const addTodo = (text) => ( { type: "ADD_TODO", id: nextTodoId++, text } ); const toggleTodo = (id) => ( { type: "TOGGLE_TODO", id } ); const setVisibilityFilter = (filter) => ( { type: "SET_VISIBILITY_FILTER", filter } );
// 默認初始狀態 const initialState = { filter: "SHOW_ALL", todos: [] }; function rootReducer(state = initialState, action) { switch (action.type) { case "ADD_TODO": // 對象解構 const { id, text } = action; return { ...state, todos: [ ...state.todos, { id, text, completed: false }, ], }; case "TOGGLE_TODO": return { ...state, todos: state.todos.map(todo => { if (todo.id !== action.id) return todo; return { ...todo, completed: !todo.completed, }; }), }; case "SET_VISIBILITY_FILTER": return { ...state, filter: action.filter, }; default: return state; } }
注意!
不要直接修改原有的 state,而是返回一個新的 state。可以使用 Object.assign() 新建一個新的 state。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter }),因為它會改變第一個參數的值。你必須把第一個參數設置為空對象。你也可以開啟對 ES7 提案對象展開運算符的支持, 從而使用 { ...state, ...newState } 達到相同的目的。
在 default 的情況下返回舊的 state,用來兼容遇到未知的 action 這樣的錯誤。
拆分 reducer
目前代碼看著比較冗長,其實在邏輯上 todos 的處理和 filter 的處理應該分開,所以在 state 沒有互相耦合時,可以將其拆分,從而讓 reducer 精細地對于對應 state 的子樹進行處理。
// 處理單個 todo const todoReducer = (state, action) => { switch (action.type) { case "ADD_TODO": return { id: action.id, text: action.text, completed: false, }; case "TOGGLE_TODO": if (state.id !== action.id) return state; return { ...state, completed: !state.completed, }; default: return state; } }; // 處理 todos const todosReducer = (state = [], action) => { switch (action.type) { case "ADD_TODO": return [ ...state, todoReducer(undefined, action), ]; case "TOGGLE_TODO": return state.map(t => todoReducer(t, action)); default: return state; }; }; // 處理 filter const filterReducer = (state = "SHOW_ALL", action) => { switch (action.type) { case "SET_VISIBILITY_FILTER": return action.filter; default: return state; }; }; const rootReducer = (state = initialState, action) => ({ todos: todosReducer(state.todos, action), filter: filterReducer(state.filter, action), });
注意觀察最后的 rootReducer 函數,返回的是一個經過各種 reducer 處理過并合并后的新 state。
然鵝,注意這里 todos: todos(state.todos, action), 傳入 state.todos,返回的一定也是 todos(因為都是 state 樹上的節點)。
所以 redux 提供了很實用的 combineReducers api,用于簡化 reducer 的合并。
import { combineReducers } from "redux"; const rootReducer = combineReducers({ todos: todosReducer, filter: filterReducer, }); // initialState 可以作為第二個參數傳入 const store = createStore(rootReducer, initialState);
并且如果 reducer 與 state 節點同名的話(即 todosReducer -> todos)還能通過 es6 的語法更進一步地簡化
import { combineReducers } from "redux"; const rootReducer = combineReducers({ todos, filter }); // initialState 可以作為第二個參數傳入 const store = createStore(rootReducer, initialState);
隨著應用的膨脹,我們還可以將拆分后的 reducer 放到不同的文件中, 以保持其獨立性并用于專門處理不同的數據域。
首先只寫一個根組件
import React, { Component } from "react"; class TodoApp extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.props; this.unsubscribe = store.subscribe( this.forceUpdate.bind(this) ); } // 取消訂閱 componentWillUnmount() { this.unsubscribe(); } // 渲染單個 todo _renderTodo(todo) { const { store } = this.props; return (
Show: {" "} {this._renderFilter("SHOW_ALL", "all")} {", "} {this._renderFilter("SHOW_COMPLETED", "completed")} {", "} {this._renderFilter("SHOW_ACTIVE", "active")}
TodoApp 只有根組件
{% iframe http://jsbin.com/bodise/edit?... 100% 800 %}
將所有界面內容全寫在 TodoApp 中實在是太臃腫了,接下來根據之前的分析結果將其分為以下子組件(全是展示組件)
AddTodo
TodoList
Todo
Footer
FilterLink
const AddTodo = ({ onAddClick }) => { let input; return (input = node} />); }; const Todo = ({ text, onClick, completed }) => (
Show:
{" "}
所以 TodoApp 精簡后是這樣~
class TodoApp extends Component { // ... render() { const { store } = this.props; const { todos, filter } = store.getState(); return (); } }{ if (!text) return; store.dispatch(addTodo(text)); }} /> store.dispatch(toggleTodo(id))} />
現在我們仍然是以 TodoApp 作為容器組件,其中各個子組件都是展示組件。
但是這樣做的話一旦子組件需要某個屬性,就需要從根組件層層傳遞下來,比如 FilterLink 中的 filter 屬性。
所以下面我們增加容器組件,讓展示組件通過容器組件獲得所需屬性。
AddTodo(container)
VisibleTodoList(container)
TodoList
Todo
Footer
FilterLink(container)
Link
// store.dispatch 又被放回來了, // 因為暫時我們只在 AddTodo 組件中使用 addTodo 這個 action // 以后增加了新的 form 之后可以考慮再將 store.dispatch 移出去 const AddTodo = ({ store }) => { let input; return (input = node} />); }; const Todo = ({ text, onClick, completed }) => (
Show:
{" "}
通過觀察重構后的代碼可以發現有三點麻煩的地方
根組件需要通過 props 將 store 傳給各個子組件
容器組件都要定義 componentDidMount 進行訂閱和 componentWillUnmount 取消訂閱
應用其實并不需要渲染所有的 todos,所以內部很麻煩地定義了 _getVisibleTodos 函數
讓我們先來解決第一個麻煩~,利用 React 提供的 context 特性
class Provider extends Component { // 通過該方法向 children 的 context 注入 store getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } // 必須要聲明傳入 context 的 store 的類型 Provider.childContextTypes = { store: React.PropTypes.object, };
自頂向下地看一下如何使用到 TodoApp 中
// 1. 使用 Provider 包裹 TodoApp,并將 store 作為 props 傳入 ReactDOM.render(, document.getElementById("container"), ); // 2. 根組件 TodoApp: 和 store say goodbye~, // 因為 TodoApp 并不是容器組件~ const TodoApp = () => ( ); // 3. AddTodo: 由于 props 固定作為第一個傳入子組件的參數, // 所以 { store } 要聲明在第二位,然鵝需要聲明 contextTypes... const AddTodo = (props, { store }) => { // ... }; // 必須聲明 AddTodo.contextTypes = { store: React.PropTypes.object, }; // 4. VisibleTodoList: 從 props 改成從 context 中獲取 store, // 同樣聲明 contextTypes... class VisibleTodoList extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.context; // props -> context // ... } // ... render() { const { store } = this.context; // props -> context const { todos, filter } = store.getState(); // ... } } // 必須聲明 VisibleTodoList.contextTypes = { store: React.PropTypes.object, }; // -- TodoList 和 Todo 不變 -- // 5. Footer:和 store say goodbye... const Footer = () => (Show: {" "}
); // 6. FilterLink: 同 VisibleTodoList(props + contextTypes...) class FilterLink extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.context; // props -> context // ... } // ... render() { const { renderFilter, children } = this.props; const { store } = this.context; // props -> context const { filter } = store.getState(); // ... } } // 必須聲明 FilterLink.contextTypes = { store: React.PropTypes.object, }; // -- Link 不變 --all {", "}completed {", "}active
現在中間的非容器組件完全不用為了自己的孩子而費勁地傳遞 store={store}
所以以上我們就實現了簡化版的由 react-redux 提供的第一個組件
然鵝,有木有覺得老寫 contextTypes 好煩啊,而且 context 特性并不穩定,所以 context 并不應該直接寫在我們的應用代碼里。
計將安出?
OOP思維:這還不簡單?寫個函數把容器組件傳進去作為父類,然后返回寫好了 componentDidMount,componentWillUnmount 和 contextTypes 的子類不就好啦~
恭喜你~面向對象的思想學的很不錯~
雖然 JavaScript 底層各種東西都是面向對象,然而在前端一旦與界面相關,照搬面向對象的方法實現起來會很麻煩...
React 早期用戶:這還不簡單?寫個 mixin 豈不美哉~~?
作為 react 親生的 mixin 確實在多組件間共享方法提供了一些便利,然而使用 mixin 的組件需要了解細節,從而避免狀態污染,所以一旦 mixin 數量多了之后會越來越難維護。
Unfortunately, we will not launch any mixin support for ES6 classes in React. That would defeat the purpose of only using idiomatic JavaScript concepts.
所以官方也放棄了在 ES6 class 中對 mixin 的支持。
函數式(FP):高階組件 High Order Component(下稱 hoc)才是終極解決方案~~
hocFactory:: W: React.Component => E: React.Component
如上所示 hoc 的構造函數接收一個 W(代表 WrappedComponent)返回一個 E(代表 Enhanced Component),而 E 就是這個高階組件。
假設我們有一個舊組件 Comp,然鵝現在接收參數有些變動。
當然你可以復制粘貼再修改舊組件的代碼...(大俠受窩一拜)
也可以這么寫,返回一個新組件來包裹舊組件。
class NewComp extends Component { mapProps(props) { return {/* new props */}; } render() { return (); } }
然鵝,如果有同樣邏輯的更多的組件需要適配呢???總不能有幾個抄幾遍吧...
所以騷年你聽說過高階組件么~?
// 先返回一個函數,而那個函數再返回新組件 const mapProps = mapFn => Comp => { return class extends Component { render() { return (); } }; }; const NewComp = mapProps(mapFn)(Comp); // 注意調用了兩次
可以看到借助高階組件我們將 mapFn 和 Comp 解耦合,這樣就算需要再嵌套多少修改邏輯都沒問題~天黑都不怕~
ok,扯了這么多的淡,終于要說到 connect 了
是噠,你木有猜錯,react-redux 提供的第二個也是最后一個 api —— connect 返回的就是一個高階組件。
使用的時候只需要 connect()(WrappedComponent) 返回的 component 自動就完成了在 componentDidMount 中訂閱 store,在 componentWillUnmount 中取消訂閱和聲明 contextTypes。
這樣就只剩下最后一個麻煩
3.應用其實并不需要渲染所有的 todos,所以內部很麻煩地定義了 _getVisibleTodos 函數
其實 connect 函數的第一個參數叫做 mapStateToProps,作用就是將 store 中的數據提前處理或過濾后作為 props 傳入內部組件,以便內部組件高效地直接調用。這樣最后一個麻煩也解決了~
然鵝,我們問自己這樣就夠了么?并沒有...
還有最后一個細節,以 FilterLink 為例。
class FilterLink extends Component { // ... render() { const { store, renderFilter, children } = this.props; const { filter } = store.getState(); return ( store.dispatch( setVisibilityFilter(renderFilter) )} > {children} ); } }
除了從 store 中獲取數據(filter),我們還從中獲取了 dispatch,以便觸發 action。如果將回調函數 onClick 的內容也加到 props 中,那么借助 connect 整個 FilterLink 的邏輯豈不是都被我們抽象完了?
是噠,connect 的第二個參數叫做 mapDispatchToProps,作用就是將各個調用到 dispatch 的地方都抽象成函數加到 props 中的傳給內部組件。這樣最后一個麻煩終于真的被解決了~
const mapStateToLinkProps = (state, ownProps) => ({ // ownProps 是原組件的 props, // 這里為了和高階組件的 props 區分 active: ownProps.renderFilter === state.filter, }); const mapDispatchToLinkProps = (dispatch, ownProps) => ({ onClick() { dispatch( setVisibilityFilter(ownProps.renderFilter) ); }, }); // 注意原 FilterLink 整個都被我們刪了 const FilterLink = connect( mapStateToLinkProps, mapDispatchToLinkProps )(Link);
TodoApp 使用 react-redux
{% iframe http://jsbin.com/fumihi/edit?... 100% 800 %}
本文從 Redux 的理論基礎和源碼出發,介紹了 Redux 的各項基礎 api。
接著一步一步地介紹如何與 React 進行結合,從過程中的各個痛點引出 react-redux 的作用和原理。
然鵝,還有好多的坑沒填,比如:大型項目的文件結構、前端路由(react-router)、中間件(middlewares)、網絡請求等各類異步操作、服務器端同構直出...
以上 to be continued...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96876.html
摘要:項目地址下載完項目然后即可基于的項目,主要是為了學習實戰。數據都是固定的,從餓了么接口臨時抓的,模擬了一個的異步數據延遲,感謝餓了么。詳細信息可以看上面的官方文檔,我這里就簡單說一下我這個項目的應用。 react-ele-webapp 項目地址 :https://github.com/kliuj/reac... run 下載完項目npm install然后npm run dev 即可 ...
摘要:不斷更新筆記效果有待進一步完善搭建一個基于的多人功能登錄注冊上傳頭像發表博文發表留言參考自前端部分以的腳手架搭起的全家桶后端采用開發環境開發環境要求以上目錄結構如何運行后端默認配置在中請確保本地端口默認可用發布到目錄中默 Full-stack-blog(不斷更新筆記) 效果Demo(有待進一步完善)搭建一個基于Koa2的多人blog功能(登錄注冊上傳頭像,發表博文,發表留言)參考自ht...
摘要:全家桶仿簡書部分功能前言前段時間接觸了下,一直想要自己寫一個小練手。在眾多應用中,考慮之后選擇了簡書來模仿,這段時間就利用了工作之余的時間進行開發。在這里簡單敘述一下我仿簡書部分布局以及功能實現的過程,僅做學習用途。 React-全家桶仿簡書部分功能 前言 前段時間接觸了下React,一直想要自己寫一個小Demo練手。在眾多應用中,考慮之后選擇了簡書來模仿,這段時間就利用了工作之余的時...
摘要:今天給大家帶來了好程序員實戰項目商城管理后臺。配合項目學習會讓你更快掌握它的使用方法下面就來看看好程序員這套實戰項目課程介紹好程序員項目本項目是一個使用開發的商城系統的管理后臺,里面登錄判斷,接口調用,數據展示和編輯,文件上傳等后臺功能。 眾所周知,項目經驗對于一個程序員變得越來越重要。在面...
摘要:本系列文章主要是介紹一些概念原理深入,適合有點基礎的初學者觀看。是狀態容器,提供可預測化的狀態管理。使用單向數據流,這意味著只能父組件傳遞給子組件。工作流工作流如下圖關于數據流的原理還有大佬不懂這個話,大佬可以點擊傳送門 本系列文章主要是介紹redux一些概念原理深入,適合有點react基礎的初學者觀看。分別講述了Reudx、React Hooks等內容。部分內容涉及源碼解析。 wh...
閱讀 3882·2021-09-10 11:22
閱讀 2347·2021-09-03 10:30
閱讀 3669·2019-08-30 15:55
閱讀 1901·2019-08-30 15:44
閱讀 849·2019-08-30 15:44
閱讀 594·2019-08-30 14:04
閱讀 3048·2019-08-29 17:18
閱讀 1272·2019-08-29 15:04