網站首頁 編程語言 正文
一、理解JavaScript純函數
1.1 純函數的概念
純函數的維基百科定義:
- 在程序設計中,若一個函數符合以下條件,那么這個函數被稱為純函數
- 此函數在相同的輸入值時,需產生相同的輸出
- 函數的輸出和輸入值以外的其他隱藏信息或狀態無關,也和由I/O設備產生的外部輸出無關
- 該函數不能有語義上可觀察的函數副作用,諸如“觸發事件”,使輸出設備輸出,或更改輸出值以外物件的內容等
純函數概念,總結如下:
- 確定的輸入,一定會產生確定的輸出
- 函數在執行過程中,不能產生副作用
案例(數組的兩個方法):
- slice:slice截取數組時不會對原數組進行任何操作,而是生成一個新的數組
- splice:splice截取數組, 會返回一個新的數組,也會對原數組進行修改
1.2 副作用概念的理解
什么是副作用?
- 副作用(side effect)表示在執行一個函數時,除了返回函數值之外,還對調用函數產生了附加的影響,比如修改了全局變量,修改參數或者改變外部的存儲
純函數在執行的過程中就是不能產生這樣的副作用:
- 副作用往往是產生bug的 “溫床”
1.3 純函數在函數式編程的重要性
為什么純函數在函數式編程中非常重要呢?
- 可以安心的編寫和安心的使用
- 在寫的時候保證了函數的純度,只是單純實現自己的業務邏輯即可,不需要關心傳入的內容是如何獲得的或者依賴其他的外部變量是否已經發生了修改
- 在用的時候,確定的輸入內容不會被任意篡改,并且自己確定的輸入,一定會有確定的輸出
-
React中就要求我們無論是函數還是class聲明一個組件,這個組件都必須像純函數一樣,保護它們的
props
不被修改 - 在下面的redux中,reducer也被要求是一個純函數
二、Redux的核心思想
2.1 為什么需要 Redux
JavaScript開發的應用程序變得越來越復雜:
- JavaScript需要管理的狀態越來越多,越來越復雜
- 這些狀態包括服務器返回的數據、緩存數據、用戶操作產生的數據等等,也包括一些UI的狀態,比如某些元素是否被選中,是否顯示加載動效,當前分頁
管理不斷變化的state是非常困難的:
- 狀態之間相互會存在依賴,一個狀態的變化會引起另一個狀態的變化,View頁面也有可能會引起狀態的變化
- 當應用程序復雜時,state在什么時候,因為什么原因而發生了變化,發生了怎么樣的變化,會變得非常難以控制和追蹤
React是在視圖層幫助我們解決了DOM的渲染過程,但是State依然是留給我們自己來管理:
- 無論是組件定義自己的state,還是組件之間的通信通過props進行傳遞;也包括通過Context進行數據之間的共享
- React主要負責幫助我們管理視圖,state如何維護最終還是我們自己來決定
- Redux就是一個幫助我們管理State的容器:Redux是JavaScript的狀態容器,提供了可預測的狀態管理
- Redux除了和React一起使用之外,它也可以和其他界面庫一起來使用(比如Vue、小程序),并且它非常小(包括依賴在內,只有2kb)
2.2 Redux的核心概念
2.2.1 store
可以定義一些初始化的數據,通過?reducer?傳入
2.2.2 action
- store 中數據的變化,必須通過派發(dispatch)action來更新
- action是一個普通的JavaScript對象,用來描述這次更新的type和content
2.2.3 reducer
將傳入的state和action結合起來生成一個新的state
2.3 Redux的三大原則
2.3.1 單一數據源
- 整個應用程序的state被存儲在一顆object tree中,并且這個object tree只存儲在一個 store 中
- Redux并沒有強制讓我們不能創建多個Store,但是那樣做并不利于數據的維護
- 單一的數據源可以讓整個應用程序的state變得方便維護、追蹤、修改
2.3.2 State是只讀的
- 唯一修改State的方法是觸發action,不要試圖在其他地方通過任何的方式來修改State
- 這樣就確保了View或網絡請求都不能直接修改state,它們只能通過action來描述自己想要如何修改state
- 這樣可以保證所有的修改都被集中化處理,并且按照嚴格的順序來執行,所以不需要擔心race condition(竟態)的問題
2.3.3 使用純函數來執行修改
- 通過reducer將 舊state和 actions聯系在一起,并且返回一個新的State
- 隨著應用程序的復雜度增加,可以將reducer拆分成多個小的reducers,分別操作不同state tree的一部分
- 但是所有的reducer都應該是純函數,不能產生任何的副作用
2.4 Redux 工作流程
建議看完Redux基本使用后再來看這幅圖:
三、Redux基本使用
注意:以下 3 部分代碼在 node 環境下
- 需要安裝redux:
npm install redux
補充:node中對ES6模塊化的支持
node?v13.2.0
開始,對ES6模塊化提供了支持:
node v13.2.0之前,需要進行如下操作:
- 在package.json中添加屬性: "type": "module"
- 在執行命令中添加如下選項:node --experimental-modules src/index.js
node v13.2.0之后,只需要進行如下操作:
- 在package.json中添加屬性: "type": "module"
-
注意:導入文件時,需要跟上
.js
后綴名
3.1 創建Store的過程
定義reducer:必須是一個純函數,不要直接修改state
createStore 傳入 reducer
const { createStore } = require('redux') // 初始化的數據 const initialState = { name: '李雷', counter: 100 } // 定義reducer函數:純函數 // 兩個參數: // 參數一:store中目前保存的state // 參數二:本次需要更新的action(dispatch傳入的action) // 返回值:返回值會作為store之后存儲的state function reducer(state = initialState, action) { switch (action.type) { case 'change_name': return { ...state, name: action.name } case 'add_numer': return { ...state, counter: state.counter + action.num } default: return state } } // 創建store const store = createStore(reducer) module.exports = store
3.2 dispatch派發action
- store 通過 dispatch 來派發 action
- 通常會有 type 屬性,也可以攜帶其他數據
const store = require('./store') console.log(store.getState()) // { name: '李雷', counter: 100 } // 修改store中的數據:必須action const nameAction = { type: 'change_name', name: '韓梅梅' } store.dispatch(nameAction) console.log(store.getState()) // { name: '韓梅梅', counter: 100 } const nameAction2 = { type: 'change_name', name: '夏洛' } store.dispatch(nameAction2) console.log(store.getState()) // { name: '夏洛', counter: 100 } // 修改counter const counterAction = { type: 'add_numer', num: 10 } store.dispatch(counterAction) console.log(store.getState()) // { name: '夏洛', counter: 110 }
3.3 subscribe定位state
-
store.subscribe()
傳入一個函數能夠監聽數據的變化 -
store.subscribe()
會返回一個函數,執行該函數取消監聽
const store = require('./store') const unSubscribe = store.subscribe(() => { console.log('訂閱數據的變化:', store.getState()) }) // 修改store中的數據:必須action store.dispatch({ type: 'change_name', name: '韓梅梅' }) store.dispatch({ type: 'change_name', name: '夏洛' }) // 取消訂閱 unSubscribe() // 修改counter store.dispatch({ type: 'add_numer', num: 10 })
3.4 代碼優化
-
優化方向:
- action的創建放到一個函數中
- 抽取到actionCreators.js文件中
- 所有的字符串常量放到constants.js文件
- reducer函數和初始化值, 放到reducer.js文件
- index.js中創建store和導出store
示例:
actionCreators.js
const { ADD_NUMBER, CHANGE_NAME } = require("./constants") const changeNameAction = (name) => ({ type: CHANGE_NAME, name }) const addNumberAction = (num) => ({ type: ADD_NUMBER, num }) module.exports = { changeNameAction, addNumberAction }
const ADD_NUMBER = "add_number" const CHANGE_NAME = "change_name" module.exports = { ADD_NUMBER, CHANGE_NAME }
const { CHANGE_NAME, ADD_NUMBER } = require('./constants') // 初始化的數據 const initialState = { name: '李雷', counter: 100 } function reducer(state = initialState, action) { switch (action.type) { case CHANGE_NAME: return { ...state, name: action.name } case ADD_NUMBER: return { ...state, counter: state.counter + action.num } default: return state } } module.exports = reducer
const { createStore } = require('redux') const reducer = require('./reducer') // 創建store const store = createStore(reducer) module.exports = store
const store = require('./store') const { changeNameAction, addNumberAction } = require('./store/actionCreators') store.dispatch(changeNameAction('獨孤月')) store.dispatch(addNumberAction(100)) console.log(store.getState()) // { name: '獨孤月', counter: 200 }
- constants.js
- reducer.js
- index.js
- util.js 中使用
四、Redux 在 React中使用
4.1 先來一個案例
有兩個組件,組件上展示同一個counter,并且兩者能夠對counter進行操作
- 創建redux對應的store文件夾
actionCreators.js
import * as actionTypes from './constants' export const addNumberAction = (num) => ({ type: actionTypes.ADD_NUMBER, num }) export const subNumberAction = (num) => ({ type: actionTypes.SUB_NUMBER, num })
constants.js
export const ADD_NUMBER = "add_number" export const SUB_NUMBER = "sub_number"
reducer.js
import * as actionTypes from './constants' const initialState = { counter: 100 } function reducer(state = initialState, action) { switch (action.type) { case actionTypes.ADD_NUMBER: return { ...state, counter: state.counter + action.num } case actionTypes.SUB_NUMBER: return { ...state, counter: state.counter - action.num } default: return state } } export default reducer
index.js
import { createStore } from "redux" import reducer from "./reducer" const store = createStore(reducer) export default store
組件中使用:
import React, { PureComponent } from 'react' // 引入store import store from '../store' import { addNumberAction } from '../store/actionCreators' export default class Home extends PureComponent { constructor() { super() this.state = { counter: store.getState().counter } } componentDidMount() { store.subscribe(() => { const state = store.getState() this.setState({ counter: state.counter }) }) } addNumber(num) { store.dispatch(addNumberAction(num)) } render() { const { counter } = this.state return ( <div> <h2>Home Counter: {counter}</h2> <div> <button onClick={e => this.addNumber(1)}>+1</button> <button onClick={e => this.addNumber(5)}>+5</button> <button onClick={e => this.addNumber(8)}>+8</button> </div> </div> ) } }
import React, { PureComponent } from 'react' // 引入store import store from '../store' import { subNumberAction } from '../store/actionCreators' export default class Profile extends PureComponent { constructor() { super() this.state = { counter: store.getState().counter } } componentDidMount() { store.subscribe(() => { const state = store.getState() this.setState({ counter: state.counter }) }) } subNumber(num) { store.dispatch(subNumberAction(num)) } render() { const { counter } = this.state return ( <div> <h2>Profile Counter: {counter}</h2> <div> <button onClick={e => this.subNumber(1)}>-1</button> <button onClick={e => this.subNumber(5)}>-5</button> <button onClick={e => this.subNumber(8)}>-8</button> </div> </div> ) } }
- componentDidMount生命周期
- store.subscribe(() => {}) => this.state => render
- 修改數據:store.dispatch(addNumberAction(num))
- Home.jsx
- Profile.jsx
4.2 react-redux使用
安裝:npm install react-redux
在使用時在入口文件中導入?Provider
,傳入 store
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { Provider } from 'react-redux'; import store from './store' const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
在 About 組件中使用:
import React, { PureComponent } from 'react' import { connect } from 'react-redux' import { addNumberAction, subNumberAction } from '../store/actionCreators' export class About extends PureComponent { clacNumber(num, isAdd) { if(isAdd) { this.props.addNumber(num) } else { this.props.subNumber(num) } } render() { const { counter } = this.props return ( <div> <h2>About Counter: {counter}</h2> <button onClick={e => this.clacNumber(6, true)}>+6</button> <button onClick={e => this.clacNumber(9, true)}>+9</button> <button onClick={e => this.clacNumber(6, false)}>-6</button> <button onClick={e => this.clacNumber(9, false)}>-9</button> </div> ) } } // connect() 返回值是一個高階組件 // function mapStateToProps(state) { // return { // counter: state.counter // } // } const mapStateToProps = (state) => ({ counter: state.counter }) const mapDispatchToProps = (dispatch) => ({ addNumber: num => dispatch(addNumberAction(num)), subNumber: num => dispatch(subNumberAction(num)) }) export default connect(mapStateToProps, mapDispatchToProps)(About)
connect():
- 傳入的第一個函數是映射當前組件所需要的數據(store中可能有很多數據,比如books、counter,而此處只需要counter)
- 傳入的第二個函數是映射?
dispatch
?到 props - 返回一個高階組件
4.3 組件中的異步操作
4.3.1 類組件生命周期中請求數據
- 在class組件的componentDidMount中發送請求
通過發起action
將請求的數據保存到store中
action方法:
export const changeBannersAction = (banners) => ({ type: actionTypes.CHANGE_BANNERS, banners }) export const changeRecommendsAction = (recommends) => ({ type: actionTypes.CHANGE_RECOMMENDS, recommends })
import * as actionTypes from './constants' const initialState = { counter: 100, banners: [], recommends: [] } function reducer(state = initialState, action) { switch (action.type) { case actionTypes.CHANGE_BANNERS: return { ...state, banners: action.banners } case actionTypes.CHANGE_RECOMMENDS: return { ...state, recommends: action.recommends } default: return state } } export default reducer
import React, { PureComponent } from 'react' import { connect } from 'react-redux' import axios from 'axios' import { changeBannersAction, changeRecommendsAction } from '../store/actionCreators' export class Category extends PureComponent { componentDidMount() { // 發送請求 axios.get('http://123.207.32.32:8000/home/multidata').then(res => { const banners = res.data.data.banner.list const recommends = res.data.data.recommend.list this.props.changeBanners(banners) this.props.changeRecommends(recommends) }) } render() { return ( <div> <h2>Category Page</h2> </div> ) } } const mapDispatchToProps = (dispacth) => ({ changeBanners: banners => dispacth(changeBannersAction(banners)), changeRecommends: recommends => dispacth(changeRecommendsAction(recommends)) }) export default connect(null, mapDispatchToProps)(Category)
- reducer
- category組件
4.3.2 使用中間件
上面的代碼有一個缺陷:
- 我們必須將網絡請求的異步代碼放到組件的生命周期中來完成
- 事實上,網絡請求到的數據也屬于狀態管理的一部分,更好的一種方式應該是將其也交給redux來管理
如何將異步請求交給 Redux?
- 一個普通的action,返回的是一個對象?
{ type: CHANGE_COUNTER, num: 10 }
- 對象中是無法直接拿到服務器請求到的異步數據的,但是如果返回一個函數呢?
- 返回一個函數,然后在組件中發起 Action 的時候,執行這個函數是不是就能夠拿到數據了呢!!!
- !!!?普通的 action 不能返回函數,可以借助中間件來增強一下,讓他支持返回一個函數,官網推薦的中間件:redux-thunk
- 中間件的目的:是在dispatch的action和最終達到的reducer之間,擴展一些自己的代碼
redux-thunk?做了什么呢
- 讓
dispatch(action函數)
中的action可以是一個函數; - 該函數會被調用,并且會傳給這個函數一個dispatch函數和getState函數;
- dispatch函數用于之后再次派發action
- getState函數考慮到我們之后的一些操作需要依賴原來的狀態,用于獲取之前的一些狀態
代碼演示:
import { createStore, applyMiddleware } from "redux" import thunk from "redux-thunk" import reducer from "./reducer" // 正常情況下 store.dispatch(object) // 想要派發函數 store.dispatch(function) // applyMiddleware 可以傳入多個中間件,","隔開 const store = createStore(reducer, applyMiddleware(thunk)) export default store
import * as actionTypes from './constants' import axios from 'axios' export const changeBannersAction = (banners) => ({ type: actionTypes.CHANGE_BANNERS, banners }) export const changeRecommendsAction = (recommends) => ({ type: actionTypes.CHANGE_RECOMMENDS, recommends }) export const fetchHomeMultidataAction = () => { // 如果是一個普通的action,需要返回action對象 // 問題: 對象中不能直接拿到從服務器請求的異步數據 // redux 不允許返回一個函數,需要中間件 return (dispatch, getState) => { // console.log(getState().counter) // 100 // 進行異步操作: 網絡請求 axios.get('http://123.207.32.32:8000/home/multidata').then(res => { const banners = res.data.data.banner.list const recommends = res.data.data.recommend.list // dispatch({type: actionTypes.CHANGE_BANNERS, banners}) // dispatch({type: actionTypes.CHANGE_RECOMMENDS, recommends}) dispatch(changeBannersAction(banners)) dispatch(changeRecommendsAction(recommends)) }) } }
import React, { PureComponent } from 'react' import { connect } from 'react-redux' import { fetchHomeMultidataAction } from '../store/actionCreators' export class Category extends PureComponent { componentDidMount() { this.props.fetchHomeMultidata() } render() { return ( <div> <h2>Category Page</h2> </div> ) } } const mapStateToProps = state => ({ counter: state.counter }) const mapDispatchToProps = (dispacth) => ({ fetchHomeMultidata: () => dispacth(fetchHomeMultidataAction()) }) export default connect(mapStateToProps, mapDispatchToProps)(Category)
- store(index.js) 中引入thunk
- actionCreators.js
- 組件中使用
4.4 redux-devtools
redux可以方便的對狀態進行跟蹤和調試
- redux官網提供了redux-devtools的工具
- 利用這個工具,可以知道每次狀態是如何被修改的,修改前后的狀態變化等等
安裝該工具需要兩步:
- 在對應的瀏覽器中安裝相關的插件(比如Chrome瀏覽器擴展商店中搜索Redux DevTools即可)
- 在redux中繼承devtools的中間件
默認該工具是未開啟的,開發環境開啟需要進行配置,生產環境千萬千萬不要打開哦!!!
import { createStore, applyMiddleware, compose } from "redux" import thunk from "redux-thunk" import reducer from "./reducer" // redux-devtools const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose; const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk))) export default store 復制代碼
4.5 模塊拆分
正常情況下,我們的 store 中應該是有不同狀態的數據,比如:購物車、用戶信息等等, 如果將所有的狀態都放到一個reducer中進行管理,隨著項目的日趨龐大,必然會造成代碼臃腫、難以維護。因此,我們可以對reducer進行拆分。
以上面提到的案例為例,抽取一個 counter 的reducer和一個 home 的reducer,再將其合并起來
分不同的模塊,每個模塊都包含自己的核心:
>reducer:接收action對象,返回最新的state
- constants:定義常量數據
- actioncreators:定義創建action對象的函數
- index:導出reducer
在?index.js
?中導入每一個模塊的內容,通過combineReducers
合并之后放入createStore
import { createStore, applyMiddleware, compose, combineReducers } from "redux" import thunk from "redux-thunk" import counterReducer from './counter' import homeReducer from './home' import userReducer from './user' // 將reducer合并到一起 const reducer = combineReducers({ counter: counterReducer, home: homeReducer, user: userReducer }) // redux-devtools const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) || compose; const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk))) export default store
combineReducers 如何實現合并呢?
- 事實上,它是將我們傳入的reducers合并到一個對象中,最終返回一個combination的函數(相當于我們的reducer函數了)
- 在執行combination函數的過程中,它會通過判斷前后返回的數據是否相同來決定返回之前的state還是新的state
- 新的state會觸發訂閱者發生對應的刷新,而舊的state可以有效的阻止訂閱者發生刷新
// combineReducers 原理 function reducer(state = {}, action) { // 返回一個對象,store中的state return { counter: counterReducer(state.counter, action), home: homeReducer(state.home, action), user: userReducer(state.user, action) } }
原文鏈接:https://juejin.cn/post/7146775467612176391
相關推薦
- 2022-06-15 詳解Python進行數據相關性分析的三種方式_python
- 2022-06-26 django中模板繼承與ModelForm實例詳解_python
- 2022-09-21 Mac安裝軟件時提示已損壞的完美解決方法_相關技巧
- 2022-10-24 Python?NumPy教程之數據類型對象詳解_python
- 2023-05-03 python?re.match函數的具體使用_python
- 2022-10-14 yum 倉庫管理 yum-config-manager
- 2022-12-15 Native?Memory?Tracking追蹤區域示例分析_React
- 2023-04-01 Android自定義View事件分發流程詳解_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支