摘要:寫在前頭簡介隨著單頁應(yīng)用開發(fā)日趨復(fù)雜,需要管理比任何時候都要多的狀態(tài)。如果一個的變化會引起另一個變化,那么當(dāng)變化時,就可能引起對應(yīng)以及另一個的變化,依次地,可能會引起另一個的變化。一些庫如試圖在視圖層禁止異步和直接操作來解決這個問題。
寫在前頭
redux 簡介
? 隨著 JavaScript 單頁應(yīng)用開發(fā)日趨復(fù)雜,JavaScript 需要管理比任何時候都要多的 state (狀態(tài))。 這些 state 可能包括服務(wù)器響應(yīng)、緩存數(shù)據(jù)、本地生成尚未持久化到服務(wù)器的數(shù)據(jù),也包括 UI 狀態(tài),如激活的路由,被選中的標簽,是否顯示加載動效或者分頁器等等。
? 管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那么當(dāng) view 變化時,就可能引起對應(yīng) model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發(fā)生了什么。state 在什么時候,由于什么原因,如何變化已然不受控制。 當(dāng)系統(tǒng)變得錯綜復(fù)雜的時候,想重現(xiàn)問題或者添加新功能就會變得舉步維艱。
? 如果這還不夠糟糕,考慮一些來自前端開發(fā)領(lǐng)域的新需求,如更新調(diào)優(yōu)、服務(wù)端渲染、路由跳轉(zhuǎn)前請求數(shù)據(jù)等等。前端開發(fā)者正在經(jīng)受前所未有的復(fù)雜性,難道就這么放棄了嗎?當(dāng)然不是。
? 這里的復(fù)雜性很大程度上來自于:我們總是將兩個難以理清的概念混淆在一起:變化和異步。 如果把二者分開,能做的很好,但混到一起,就變得一團糟。一些庫如 React 試圖在視圖層禁止異步和直接操作 DOM 來解決這個問題。美中不足的是,React 依舊把處理 state 中數(shù)據(jù)的問題留給了我們自己。而 redux 就可以來幫我管理這些狀態(tài);
demo 演示
demo 結(jié)構(gòu)樹
├── config-overrides.js ├── .gitignore ├── package.json ├── package-lock.json ├── public │?? ├── favicon.ico │?? ├── index.html │?? └── manifest.json ├── README.md └── src ├── App.js ├── Demo │?? ├── actionCreate.js │?? ├── Demo.jsx │?? ├── react-redux.js │?? ├── reducer.js │?? ├── redux.js │?? ├── style.css │?? └── thunk.js └── index.js一、 redux API createStore 的實現(xiàn)
? 首先我們先結(jié)合 reducer 以及 action 的知識簡單實現(xiàn)開頭展示的 demo, 并逐步揭曉 createStore 的神秘面紗;
創(chuàng)建 reducer 并導(dǎo)出 reducer
// reducer.js const initState = { user: "qianyin", age: 18, sex: "男" }; export const reducer = (state=initState, action) => { switch(action.type){ case "USER_UPDATE": return {...state, ...action.payload}; case "AGE_GROW": return {...state, age: state.age + 1}; case "SEX_UPDATE": return {...state, ...action.payload}; default: return state; } }
創(chuàng)建 action 創(chuàng)建函數(shù)
// actionCreate.js export const changeUser = (user) => { return { payload:{user}, type: "USER_UPDATE", }; } export const changeAge = () => { return { type: "AGE_GROW" }; }
通過 react 在頁面上預(yù)先繪制出基本的元素
/* style.css */ .btn{ height: 31px; } .input{ height: 25px; }
// Demo.jsx import React from "react"; import "./style.css"; export default class Demo extends React.Component{ onChange = () => {} onClick = () => {} render(){ return (); } }user: xxx, age: xxx
user:
最終頁面將渲染如下:
創(chuàng)建全局狀態(tài) state;
創(chuàng)建監(jiān)聽隊列;
針對監(jiān)聽隊列,新增函數(shù)用于將指定監(jiān)聽對象添加到隊列中;
在函數(shù) dispatch 中執(zhí)行 reducer 將返回值作為新的 state, 同時依次執(zhí)行監(jiān)聽對象;
默認執(zhí)行一次 dispatch 給定一個 type 相對唯一的 action, 目的是為了匹配 reducer 的默認狀態(tài)值,從而實現(xiàn)對 redux state 的初始化;
在組件 Demo 通過在函數(shù) update 使用 this.setState 將全局 state 保存到 react state 中,并將函數(shù) update 添加到監(jiān)聽隊列中;從而使得當(dāng)我們一旦試圖通過 dispatch 修改全局狀態(tài)時,能夠及時更新 react state 最終觸發(fā) react 生命周期 render;
在 react 生命周期 componentDidMount 中我們除了將 update 添加到監(jiān)聽隊列以外,還需手動執(zhí)行一次 update 其主要目的就是為了首次初始化 react state;
// Demo.jsx import React from "react"; import { changeAge, changeUser } from "./actionCreate"; import { reducer } from "./reducer"; import "./style.css"; let state; const listeners = []; const subscribe = (listener) => { listeners.push(listener); } const dispatch = (action) => { state = reducer(state, action); console.log(state); listeners.forEach(v => v()); } dispatch({type: "%$&HJKAJJHDJHJ"}); export default class Demo extends React.Component{ state = {user: "xxx", age: "xxx"}; componentDidMount(){ subscribe(this.update); this.update(); } update = () => { this.setState(state); } onChange = (e) => { dispatch(changeUser(e.target.value)); } onClick = () => { dispatch(changeAge()); } render(){ return (); } }user: {this.state.user}, age: {this.state.age}
user:
? 其實上文的代碼中對于 createStore 的實現(xiàn)原理已經(jīng)基本描述清除,下面我們只是單純的對代碼進行了簡單的封裝;當(dāng)然為了能夠獲取到 state 我們專門增加了一個函數(shù) getState 來實現(xiàn)它;
createStore 函數(shù)實現(xiàn)
// redux.js export const createStore = (reducer) => { // 聲明常量 let state; const listeners = []; // 獲取狀態(tài) const getState = () => { return state; } // 添加監(jiān)聽對象 const subscribe = (listener) => { listeners.push(listener); } // [1]執(zhí)行reducer修改狀態(tài) [2]遍歷執(zhí)行監(jiān)聽對象 const dispatch = (action) => { state = reducer(state, action); listeners.forEach(v => v()); } // 初始化 state dispatch({type: "%$&HJKAJJHDJHJ"}); // 暴露接口 return {getState, subscribe, dispatch}; }
調(diào)用 createStore 并對 demo 進行修改
// Demo.jsx import React from "react"; import "./style.css"; import { changeAge, changeUser } from "./actionCreate"; import { reducer } from "./reducer"; import { createStore } from "./redux"; const store = createStore(reducer); export default class Demo extends React.Component{ state = {user: "xxx", age: "xxx"}; componentDidMount(){ store.subscribe(this.update); this.update(); } update = () => { this.setState(store.getState()); } onChange = (e) => { store.dispatch(changeUser(e.target.value)); } onClick = () => { store.dispatch(changeAge()); } render(){ return (二、 react-redux API Provider 的實現(xiàn)); } }user: {this.state.user}, age: {this.state.age}
user:
? 在 react 中大多數(shù)情況下我們需要將狀態(tài)傳遞給后代組件進行使用的,當(dāng)然通過 props 是可以實現(xiàn)狀態(tài)從父級到子級的傳遞,但是當(dāng)狀態(tài)需要傳遞的層級比較深的情況下再使用 props 就顯得無力了,那么在 react-redux 中它是如何實現(xiàn)對 store 的傳遞的呢?
在 App.js 中創(chuàng)建 store 并通過 context 傳遞 store
// App.js import React, { Component } from "react"; import propTypes from "prop-types"; import { createStore } from "./Demo/redux"; import { reducer } from "./Demo/reducer"; import Demo from "./Demo/Demo"; // 創(chuàng)建 store const store = createStore(reducer); class App extends Component { // 聲明 childContextTypes 狀態(tài)屬性類型 static childContextTypes = { store: propTypes.object }; // 設(shè)置 childContext getChildContext(){ return {store} } render() { return; } } export default App;
在子組件 Demo 中通過 context 獲取 store 并對代碼進行簡單修改
// Demo.jsx import React from "react"; import propTypes from "prop-types"; import "./style.css"; import { changeAge, changeUser } from "./actionCreate"; export default class Demo extends React.Component{ // 設(shè)置 context 狀態(tài)值類型 static contextTypes = { store: propTypes.object }; constructor(props, context){ super(props, context); // 獲取store this.store = context.store; this.state = {user: "xxx", age: "xxx"}; } componentDidMount(){ this.store.subscribe(this.update); this.update(); } update = () => { this.setState(this.store.getState()); } onChange = (e) => { this.store.dispatch(changeUser(e.target.value)); } onClick = () => { this.store.dispatch(changeAge()); } render(){ return (); } }user: {this.state.user}, age: {this.state.age}
user:
? 通過 react context 我們實現(xiàn)了對 store 的傳遞,到這里 Provider 的功能以及實現(xiàn)原理基本上應(yīng)該算是清晰了,無非就是對組件進行包裹同時通過 react context 來傳遞共享 store;那么接下來我們通過對代碼的封裝來實現(xiàn) Provider 組件;
Provider 組件:實現(xiàn)對 store 的傳遞
// react-redux.js import React from "react"; import propTypes from "prop-types"; export class Provider extends React.Component{ // 設(shè)置 childContext 狀態(tài)值類型 static childContextTypes = { store: propTypes.object }; // 設(shè)置 childContext getChildContext(){ return {store: this.props.store} } render(){ return this.props.children; } }
重寫 App.js: 對 Provider 組件的調(diào)用
// App.js import React, { Component } from "react"; import { createStore } from "./Demo/redux"; import { Provider } from "./Demo/react-redux"; import { reducer } from "./Demo/reducer"; import Demo from "./Demo/Demo"; // 創(chuàng)建 store const store = createStore(reducer); class App extends Component { render() { // 調(diào)用接口 Provider return三、 react-redux API connect 高階組件的實現(xiàn); } } export default App;
? 上文中在后代組件如果需要獲取 store 則需要手動通過獲取 react context 來調(diào)用 store 并且需要顯性的調(diào)用 store 內(nèi)部的方法來進行一些操作;接下來我們來實現(xiàn)這么一個高階組件 connect,我們只需要提供所需的 redux state 以及 action 創(chuàng)建函數(shù),即可通過 props 獲取到相應(yīng)的 redux state , 并且允許直接通過 props 調(diào)用action 創(chuàng)建函數(shù)來試圖修改 redux state ;
創(chuàng)建高階組件 connect
// react-redux.js export const connect = (mapStateToProps, mapDispatchToProps) => (Component) => { return class NewComponent extends React.Component{ render(){ return} } }
獲取store
// react-redux.js export const connect = (mapStateToProps, mapDispatchToProps) => (Component) => { return class NewComponent extends React.Component{ // 設(shè)置 context 狀態(tài)值類型 static contextType = { store: propTypes.object }; // [1]獲取 store [2]設(shè)置空 react state constructor(props, context){ super(props, context); this.store = context.store; this.state = {}; } render(){ return} } }
添加監(jiān)聽對象,并嘗試通過 props 將狀態(tài)傳遞給子組件
export const connect = (mapStateToProps, mapDispatchToProps) => (Component) => { return class NewComponent extends React.Component{ static contextType = { store: propTypes.object }; constructor(props, context){ super(props, context); this.store = context.store; this.state = {}; } // [1]添加監(jiān)聽對象 [2]手動執(zhí)行監(jiān)聽對象,初始化 react state componentDidMount(){ this.store.subscribe(this.update); this.update(); } update = () => { // 獲取全部redux state 并添加到 react state const state = this.store.getState(); this.setState(state); } render(){ // 通過 props 將 react state 全部傳給子組件 return} } }
通過 mapStateToProps 獲取指定 redux state
// react-redux.js export const connect = (mapStateToProps, mapDispatchToProps) => (Component) => { return class NewComponent extends React.Component{ static contextType = { store: propTypes.object }; constructor(props, context){ super(props, context); this.store = context.store; this.state = {}; } componentDidMount(){ this.store.subscribe(this.update); this.update(); } update = () => { // 執(zhí)行 mapStateToProps 只獲取用戶指定需求的 state const state = this.store.getState(); const filterState = mapStateToProps(state); this.setState(filterState); } render(){ return} } }
通過 mapDispatchToProps 獲取 action 創(chuàng)建函數(shù): 使用 dispatch 包裹后返回
// react-redux.js // react-redux.js export const connect = (mapStateToProps, mapDispatchToProps) => (Component) => { return class NewComponent extends React.Component{ static contextTypes = { store: propTypes.object }; constructor(props, context){ super(props, context); this.store = context.store; this.state = {}; } componentDidMount(){ this.store.subscribe(this.update); this.update(); } update = () => { // 處理 state ===> 獲取用戶指定的 state const state = this.store.getState(); const filterState = mapStateToProps(state); // 使用 dispatch 對 mapDispatchToProps 中的 action 創(chuàng)建函數(shù)進行包裹后返回 const actionFun = {}; for(let key in mapDispatchToProps){ actionFun[key] = (...args) => { this.store.dispatch(mapDispatchToProps[key](...args)); } } // 一種簡寫方式: 騷操作 // const actionFun = Object.keys(mapDispatchToProps) // .reduce((total, item) => { // return { ...total, [item]: (...args) => {dispatch(mapDispatchToProps[item](...args));} // } } ,{}); this.setState({...filterState, ...actionFun}); } render(){ return} } }
調(diào)用高階組件:修改 Demo.jsx
// Demo.jsx import React from "react"; import { changeAge, changeUser } from "./actionCreate"; import { connect } from "./react-redux"; import "./style.css"; // 編寫 mapStateToProps 參數(shù) redux state 返回所需的 redux state const mapStateToProps = (state) => { return {user: state.user, age: state.age}; } // 調(diào)用高階組件 @connect(mapStateToProps, {changeAge, changeUser}) export default class Demo extends React.Component{ onChange = (e) => { this.props.changeUser(e.target.value); } onClick = () => { this.props.changeAge(); } render(){ return (四、redux API bindactioncreators 的實現(xiàn)); } }user: {this.props.user}, age: {this.props.age}
user:
? 在上文我們對 mapDispatchToProps 的處理過程就是 API bindactioncreators 的功能: 將給定 action 創(chuàng)建函數(shù)使用 dispatch 進行包裹后返回;
封裝 bindactioncreators
// redux.js export const bindactioncreators = (mapDispatchToProps, dispatch) => { const actionFun = {}; // 遍歷 mapDispatchToProps 中每個 action 創(chuàng)建函數(shù) 并使用 dispatch 包裹后返回 for(let key in mapDispatchToProps){ actionFun[key] = (...args) => { dispatch(mapDispatchToProps[key](...args)); } } return actionFun; // 一種簡寫方式: 騷操作 // return actionFun = Object.keys(mapDispatchToProps) // .reduce((total, item) => { // return { ...total, [item]: (...args) => {dispatch(mapDispatchToProps[item](...args));} // } } ,{}); }
修改 connect :
// react-redux.js import { bindactioncreators } from "./redux"; .... export const connect = (mapStateToProps, mapDispatchToProps) => (Component) => { return class NewComponent extends React.Component{ static contextTypes = { store: propTypes.object }; constructor(props, context){ super(props, context); this.store = context.store; this.state = {}; } componentDidMount(){ this.store.subscribe(this.update); this.update(); } update = () => { const state = this.store.getState(); const filterState = mapStateToProps(state); // 調(diào)用 API bindactioncreators // 對 mapDispatchToProps 內(nèi)每個 action 創(chuàng)建函數(shù)使用 dispatch 進行包裹后返回 const actionFun = bindactioncreators(mapDispatchToProps, this.store.dispatch); this.setState({...filterState, ...actionFun}); } render(){ return五、redux API applyMiddleware 的實現(xiàn)} } }
? 到此簡化版的 react-redux 算是已經(jīng)初步完成,但是假如我們想要我們的 age 值的增長是一個異步操作,比如:通過按鈕點擊后經(jīng)過兩秒再修改 age ,而不是一點擊按鈕就立即修改值;這樣我們又該怎么實現(xiàn)呢?
? 當(dāng)然我們可以通過 setTimeout 兩秒后再執(zhí)行 action 創(chuàng)建函數(shù),比如這樣:
onClick = () => { setTimeout(()=>{ // 兩秒后執(zhí)行 action 創(chuàng)建函數(shù) this.props.changeAge(); }, 2000); }
? 但是呢事實上我們并不愿意像上面那么整,我們想要這么一種效果:我們只需要簡單的調(diào)用 action 創(chuàng)建函數(shù)即可實現(xiàn)異步操作,而不是需要進行額外的操作;這時我們就需要為我們的 react-redux 編寫一個中間件來實現(xiàn)這么一個效果;
新增action 創(chuàng)建函數(shù)
? 在這之前我們所有的 acton 創(chuàng)建函數(shù)都是直接返回一個 action 對象,下面我們寫一個不一樣的 action 創(chuàng)建函數(shù), 它返回的不再是一個 action 對象而是一個函數(shù),并且該函數(shù)接收兩個參數(shù) dispatch 以及 getState, 在該函數(shù)內(nèi)部我們進行相應(yīng)的異步操作,比如:修改 age 值;
// actionCreate.js export const asyncChangeAge = () => { // 返回函數(shù) return (dispatch, getState) => { setTimeout(v=>{ console.log("==>", getState()); dispatch({type: "AGE_GROW"}); }, 1000); } }
修改頁面:新增按鈕并綁定點擊事件,并且調(diào)用 asyncChangeAge 函數(shù);
// Demo.jsx // Demo.jsx import React from "react"; import "./style.css"; // 導(dǎo)入 asyncChangeAge import { changeAge, changeUser, asyncChangeAge } from "./actionCreate"; import { connect } from "./react-redux"; const mapStateToProps = (state) => { return {user: state.user, age: state.age}; } // 添加 asyncChangeAge @connect(mapStateToProps, {changeAge, changeUser, asyncChangeAge}) export default class Demo extends React.Component{ onChange = (e) => { this.props.changeUser(e.target.value); } onClick = () => { this.props.changeAge(); } // 點擊事件 onClickAsync = () => { this.props.asyncChangeAge(); } render(){ return (); } }user: {this.props.user}, age: {this.props.age}
user: {/* 新增按鈕 */}
頁面的變化其實很簡單就是新增了一個按鈕:
? 接下來我們先什么都不考慮先來實現(xiàn)我們的需求,現(xiàn)在 action 創(chuàng)建函數(shù) asyncChangeAge 因為返回的是一個對象,其 type 值為 undefined 所以當(dāng)我們點擊按鈕時 reducer 將會一直匹配到默認情況,返回的將是當(dāng)前的狀態(tài),接下來我們先讓我們的 action 創(chuàng)建函數(shù) asyncChangeAge 生效,達到異步修改狀態(tài)的作用;
擴展 createStore
? 既然 asyncChangeAge 返回的不再是一個 action 對象,而是一個函數(shù);那么其實我們要做的事情是很簡單的,我們只需要針對 createStore 中的返回值 dispatch 進行一個簡單的擴展即可;通過判斷 dispatch 中的 action 參數(shù)是否是函數(shù)而進行不同的操作:
// redux.js export const createStore = (reducer) => { ...... // 在createStore 我們對返回值 dispatch 進行了封裝 const dispatchExtend = (action) => { if(typeof action === "function"){ // action 為函數(shù),執(zhí)行函數(shù) action(dispatch, getState); } else { // action 為非函數(shù)(對象)調(diào)用dispatch dispatch(action); } } return {getState, dispatch: dispatchExtend, subscribe}; }
? 上文我們通過對 createStore 的返回值 dispatch 進行了擴展,實現(xiàn)了 redux-react 的異步操作,但問題是我們將代碼寫死在 createStore 中了,redux-react 的異步操作應(yīng)該是一個可選項而不應(yīng)該是必選項;
重新擴展 createStore :
? 新增參數(shù) middleware (函數(shù)), 在函數(shù) createStore 開始位置判斷 middleware 是否存在,存在則執(zhí)行;
// redux.js export const createStore = (reducer, middleware) => { // 判斷 middleware 是否存在,存在則執(zhí)行 if(middleware){ return middleware(createStore)(reducer); } let state; const listeners = []; const getState = () => { return state; } const dispatch = (action) => { state = reducer(state, action); listeners.forEach(v => v()); } const subscribe = (listener) => { listeners.push(listener); } dispatch({type: "%$&HJKAJJHDJHJ"}); return {getState, dispatch, subscribe}; }
編寫函數(shù) applyMiddleware ,在創(chuàng)建 store 時 作為 createStore 第二參數(shù)
// App.js const applyMiddleware = (createStore) => (redux)=> { // 在這里進行創(chuàng)建 store const store = createStore(redux); // 返回store return {...store} } const store = createStore(reducer, applyMiddleware);
在 applyMiddleware 函數(shù)內(nèi)擴展 dispatch
? 上文 applyMiddleware 函數(shù)并其實沒做任何事情, 只是在 createStore 函數(shù)外面套了一層函數(shù),那么接下來我們做點正事,來擴展一下我們的 dispatch
// App.js const applyMiddleware = (createStore) => (redux)=> { const store = createStore(redux); const midApi = { getState: store.getState, dispatch: (...args) => {dispatch(...args);} }; const dispatch = (action) => { if( typeof action === "function" ){ action(midApi.dispatch, midApi.getState); } else { store.dispatch(action); } } return { ...store, dispatch }; }
現(xiàn)在我們來看看效果:
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97070.html
摘要:簡介創(chuàng)建的函數(shù),返回一個對象,包含等方法合并多個中間件處理,在實際的前調(diào)用一系列中間件,類似于綁定和函數(shù)式編程中常見的方法,介紹官方提供的綁定庫。 前言 在學(xué)習(xí)了React之后, 緊跟著而來的就是Redux了~ 在系統(tǒng)性的學(xué)習(xí)一個東西的時候, 了解其背景、設(shè)計以及解決了什么問題都是非常必要的。接下來記錄的是, 我個人在學(xué)習(xí)Redux時的一些雜七雜八~ Redux是什么 通俗理解 h...
摘要:或者兄弟組件之間想要共享某些數(shù)據(jù),也不是很方便傳遞獲取等。后面要講到的就是通過讓各個子組件拿到中的數(shù)據(jù)的。所以,確實和沒有什么本質(zhì)關(guān)系,可以結(jié)合其他庫正常使用。 本文介紹了react、redux、react-redux之間的關(guān)系,分享給大家,也給自己留個筆記,具體如下: React 一些小型項目,只使用 React 完全夠用了,數(shù)據(jù)管理使用props、state即可,那什么時候需要引入...
摘要:本文轉(zhuǎn)載至今日頭條技術(shù)博客眾所周知,的單向數(shù)據(jù)流模式導(dǎo)致狀態(tài)只能一級一級的由父組件傳遞到子組件,在大中型應(yīng)用中較為繁瑣不好管理,通常我們需要使用來幫助我們進行管理,然而隨著的發(fā)布,新成為了新的選擇。 本文轉(zhuǎn)載至:今日頭條技術(shù)博客showImg(https://segmentfault.com/img/bVbiNJO?w=900&h=383);眾所周知,React的單向數(shù)據(jù)流模式導(dǎo)致狀態(tài)...
摘要:我們可以為元素添加屬性然后在回調(diào)函數(shù)中接受該元素在樹中的句柄,該值會作為回調(diào)函數(shù)的第一個參數(shù)返回。使用最常見的用法就是傳入一個對象。單向數(shù)據(jù)流,比較有序,有便于管理,它隨著視圖庫的開發(fā)而被概念化。 面試中問框架,經(jīng)常會問到一些原理性的東西,明明一直在用,也知道怎么用, 但面試時卻答不上來,也是挺尷尬的,就干脆把react相關(guān)的問題查了下資料,再按自己的理解整理了下這些答案。 reac...
閱讀 1618·2021-11-22 13:53
閱讀 2848·2021-11-15 18:10
閱讀 2755·2021-09-23 11:21
閱讀 2491·2019-08-30 15:55
閱讀 475·2019-08-30 13:02
閱讀 752·2019-08-29 17:22
閱讀 1659·2019-08-29 13:56
閱讀 3455·2019-08-29 11:31