React狀態(tài)管理Redux原理與介紹
一、Redux
和vuex一樣,redux的出現(xiàn)是為了管理web應(yīng)用的公共狀態(tài)。
這些 state 可能包括服務(wù)器響應(yīng)、緩存數(shù)據(jù)、本地生成尚未持久化到服務(wù)器的數(shù)據(jù),也包括 UI 狀態(tài),如激活的路由,被選中的標簽,是否顯示加載動效或者分頁器等等。
二、Redux的組成
2.1 store
store 就是保存數(shù)據(jù)的地方,整個應(yīng)用只能有一個 store,可以理解為一個存儲數(shù)據(jù)的倉庫。
redux 提供 createStore
這個函數(shù),用來創(chuàng)建一個store 以存放整個應(yīng)用的 state:
import { createStore } from 'redux'; const store = createStore(reducer, [preloadedState], [enhancer]);
可以看到,createStore
接受 reducer
、初始 state(可選)
和增強器(可選)作為參數(shù),返回一個新的 store 對象
.
2.2 state
state就是store 對象包含所有數(shù)據(jù),如果要獲取當前時刻的 state,可以通過 store.getState() 方法拿到:
import { createStore } from 'redux'; const store = createStore(reducer, [preloadedState], [enhancer]); const state = store.getState();
2.3 action
- state 的變化,會導(dǎo)致視圖的變化。但是,用戶接觸不到 state,只能接觸到視圖。所以,state 的變化必須是由視圖發(fā)起的。
- action 就是視圖發(fā)出的通知,通知store此時的 state 應(yīng)該要發(fā)生變化了。
- action 是一個對象。其中的 type屬性是必須的,表示 action 的名稱。其他屬性可以自由設(shè)置,社區(qū)有一個規(guī)范可以參考:
const action = { type: 'ADD_TODO', payload: 'Learn Redux' // 可選屬性 可自定義名稱 };
所以action可以理解為視圖層向store發(fā)送的一個命令(通知),它包含了需要執(zhí)行的事件(type屬性)以及傳遞的數(shù)據(jù)(自定義屬性)。
2.4 reducer
- store 收到 action 以后,必須給出一個新的 state,這樣視圖才會進行更新。state 的計算(更新)過程則是通過reducer 實現(xiàn)。
- reducer 是一個函數(shù),它接受 action 和當前 state 作為參數(shù),返回一個新的 state:
const reducer = function (state = initState, action) { // ... return new_state; };
創(chuàng)建store時,第一個參數(shù)就是reducer:
const store = createStore(reducer);
那么如何向store發(fā)送action呢?
store.dispatch({ type: 'ADD_TODO', payload: 'Learn Redux' });
store對象擁有dispath
方法發(fā)送action,參數(shù)就是需要傳遞的action對象,然后reducer接收到action并處理,返回新的state,視圖自動更新。
三、三大原則
Redux 可以用這三個基本原則來描述:
3.1 單一數(shù)據(jù)源
整個應(yīng)用的 state 被儲存在一棵 object tree
中,并且這個 object tree 只存在于唯一一個 store 中。
console.log(store.getState()) /* 輸出 { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] } */
3.2 State只讀
唯一改變 state 的方法就是觸發(fā) action
,action 是一個用于描述已發(fā)生事件的普通對象。
這樣確保了視圖和網(wǎng)絡(luò)請求都不能直接修改 state,相反它們只能表達想要修改的意圖。因為所有的修改都被集中化處理,且嚴格按照一個接一個的順序執(zhí)行,因此不用擔(dān)心競態(tài)條件(race condition)的出現(xiàn)。 Action 就是普通對象而已,因此它們可以被日志打印、序列化、儲存、后期調(diào)試或測試時回放出來。
store.dispatch({ type: 'COMPLETE_TODO', index: 1 }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' })
所以返回新的state時不能直接修改參數(shù)state,而是在不修改參數(shù)state的基礎(chǔ)上返回新的state。
例如完成一個新增todo的功能:
case 'ADD_TODO': return state.push({ text: action.text, completed: false })
這樣是不會生效的,應(yīng)為這樣直接修改了state的值,正確的做法應(yīng)該是:
case 'ADD_TODO': return [ ...state, { text: action.text, completed: false } ]
這里使用了擴展運算符(…)將數(shù)組展開然后和新增的todo合并,對象同樣可以使用擴展運算符達到新增屬性的目的。
3.3 使用純函數(shù)修改State
為了描述 action 如何改變 state tree ,你需要編寫 reducers
。
Reducer 只是一些純函數(shù),它接收先前的 state
和 action
,并返回新的 state。剛開始你可以只有一個 reducer,隨著應(yīng)用變大,你可以把它拆成多個小的 reducers,分別獨立地操作 state tree 的不同部分,因為 reducer 只是函數(shù),你可以控制它們被調(diào)用的順序,傳入附加數(shù)據(jù),甚至編寫可復(fù)用的 reducer 來處理一些通用任務(wù),如分頁器。
function visibilityFilter(state = 'SHOW_ALL', action) { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter default: return state } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return [ ...state, { text: action.text, completed: false } ] case 'COMPLETE_TODO': return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: true }) } return todo }) default: return state } } import { combineReducers, createStore } from 'redux' //合并reducer let reducer = combineReducers({ visibilityFilter, todos }) //利用reducer創(chuàng)建store let store = createStore(reducer)
四、基于Redux的TodoList
效果:
todes的reducer:
const initTodos = [ { id: 1, text: "睡覺??", completed: false, }, { id: 2, text: "吃飯??", completed: false, }, { id: 3, text: "打豆豆??", completed: true, }, ]; let nextTodoID = 4; const todos = (state = initTodos, action) => { switch (action.type) { case "ADD_TODO": return [ ...state, { id: nextTodoID++, text: action.text, completed: false, }, ]; case "TOGGLE_TODO": return state.map((todo) => todo.id === action.id ? { ...todo, completed: !todo.completed } : todo ); default: return state; } }; export default todos;
filter的reducer:
const visibilityFilter = (state = "SHOW_ALL", action) => { switch (action.type) { case "SET_VISIBILITY_FILTER": return action.filter; default: return state; } }; export default visibilityFilter;
詳細代碼地址:
https://github.com/YancyZhang30/react-redux-todos.git
五、react-redux
redux 并不是react專有的,其本身是一個可以結(jié)合 react,vue,angular 甚至是原生 javaScript 應(yīng)用使用的狀態(tài)庫。
react-redux是react官方提供了 react的redux適配庫(這個庫是可以選用的,也可以只用redux),使得我們能夠更好地在react應(yīng)用中使用redux來進行全局狀態(tài)管理,使用react-redux主要是為了解決組件每次使用store中的數(shù)據(jù)時都必須先使用store.getState()
來獲取state,然后必須使用store.subscribe()
進行訂閱的問題。
react-redux 將所有組件分成 UI 組件和容器組件兩大類:
1、 UI 組件只負責(zé) UI 的呈現(xiàn),不含有狀態(tài)(this.state),所有數(shù)據(jù)都由 this.props
提供,且不使用任何 redux 的 API。
2、容器組件負責(zé)管理數(shù)據(jù)和業(yè)務(wù)邏輯,含有狀態(tài)(this.state),可使用 redux 的 API。
5.1 connect方法
react-redux 提供了 connect 方法,用于將 UI 組件生成容器組件,所以如果組件想要使用store中的state,就必須先使用connect方法與store進行連接:
import {connect} from 'react-redux' const Counter = (props) => { return ( <div> <p>計數(shù)器: {props.num}</p> <div> <button onClick={props.increatement}>加</button> | <button onClick={props.decreate}>減</button><br/> </div> </div> ) } //讀取數(shù)據(jù) const mapStateToProps=(state)=>{ return{ num:state } } //進行觸發(fā)action const mapDispathToProps=(dispatch)=>{ return { increatement:()=>{dispatch( { type:"inCreateNum", num:10 } )}, decreate:()=>{dispatch( { type:"descment", num:10 } )} } } // connect將組件與store連接。connect里的參數(shù)順序不能顛倒 export default connect(mapStateToProps,mapDispathToProps)(Counter)
connect(mapStateToProps,mapDispathToProps)(Counter)
中:
- mapStateToProps:mapStateToProps 是一個函數(shù),它的作用就是建立一個從 state對象(外部)到 UI 組件 props對象的映射關(guān)系。該函數(shù)會訂閱 整個應(yīng)用的 store,每當 state 更新的時候,就會自動執(zhí)行,重新計算 UI 組件的參數(shù),從而觸發(fā) UI 組件的重新渲染。還可以使用第二個參數(shù)(可選),代表容器組件的 props 對象
- mapDispathToProps:mapDispatchToProps 是 connect 函數(shù)的第二個參數(shù),用來建立 UI 組件的參數(shù)到 store.dispatch 方法的映射。
- Counter:需要變成容器組件的UI組件,也就是需要連接store的組件。
5.2 Provider組件
- 使用 connect 方法生成容器組件以后,需要讓容器組件拿到 state 對象,才能生成 UI 組件 的參數(shù)。
- react-redux提供了 Provider 組件,可以讓容器組件拿到 state,注意只有被Provider組件包含的組件才能拿到state。
main.jsx:
import React from 'react' import ReactDOM from 'react-dom/client' import { Provider } from 'react-redux' import Counter from "./Counter"; import ShowCounter from "./ShowCounter"; import reducer from "./store/counter"; import { legacy_createStore as createStore } from "redux"; const store = createStore(reducer); ReactDOM.createRoot(document.getElementById('root')).render( <Provider store={store}> <React.StrictMode> <Counter></Counter> <ShowCounter></ShowCounter> </React.StrictMode> </Provider> )
此時Counter組件和ShowCounter組件都可以拿到state。
ShowCounter.jsx:
import React from "react"; import {connect} from "react-redux"; const ShowCounter = (props) => { return ( <div> <p>來自計數(shù)器的數(shù)據(jù):<span style={{color: 'red'}}>{props.num}</span></p> </div> ) } //讀取數(shù)據(jù) const mapStateToProps=(state)=>{ return{ num:state } } // connect將組件與store連接。connect里的參數(shù)順序不能顛倒 export default connect(mapStateToProps)(ShowCounter)
由于ShowCounter組件并不需要修改store,所以mapDispathToProps參數(shù)可以直接省略了。
效果:
到此這篇關(guān)于React狀態(tài)管理Redux原理與介紹的文章就介紹到這了,更多相關(guān)React Redux內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React實現(xiàn)antdM的級聯(lián)菜單實例
這篇文章主要為大家介紹了React實現(xiàn)antdM的級聯(lián)菜單實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10React 項目中動態(tài)設(shè)置環(huán)境變量
本文主要介紹了React 項目中動態(tài)設(shè)置環(huán)境變量,本文將介紹兩種常用的方法,使用 dotenv 庫和通過命令行參數(shù)傳遞環(huán)境變量,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04React路由的history對象的插件history的使用解讀
這篇文章主要介紹了React路由的history對象的插件history的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10React路由參數(shù)傳遞與嵌套路由的實現(xiàn)詳細講解
這篇文章主要介紹了React路由參數(shù)傳遞與嵌套路由的實現(xiàn),嵌套路由原則是可以無限嵌套,但是必須要讓使用二級路由的一級路由匹配到,否則不顯示,需要的朋友可以參考一下2022-09-09