摘要:要實現先搞清楚的就是這么一個東西,某個組件只要往自己的里面放了某些狀態,這個組件之下的所有子組件都直接訪問這個狀態而不需要通過中間組件的傳遞。
1.要實現redux,先搞清楚context
React.js 的 context 就是這么一個東西,某個組件只要往自己的 context 里面放了某些狀態,這個組件之下的所有子組件都直接訪問這個狀態而不需要通過中間組件的傳遞。一個組件的 context 只有它的子組件能夠訪問,它的父組件是不能訪問到的
//現在我們修改 Index,讓它往自己的 context 里面放一個 themeColor class Index extends Component { static childContextTypes = {//要給組件設置 context,那么 childContextTypes 是必寫的 themeColor: PropTypes.string } constructor () { super() this.state = { themeColor: "red" } } getChildContext () { return { themeColor: this.state.themeColor } } render () { return () } }
//子組件直接獲取context里面的東西 //子組件要獲取 context 里面的內容的話,就必須寫 contextTypes 來聲明和驗證你需要獲取的狀態的類型, //它也是必寫的 class Title extends Component { static contextTypes = { themeColor: PropTypes.string } render () { return (2.實現共享狀態優化React.js
) } }
context 打破了組件和組件之間通過 props 傳遞數據的規范,極大地增強了組件之間的耦合性。而且,就如全局變量一樣,context 里面的數據能被隨意接觸就能被隨意修改,每個組件都能夠改 context 里面的內容會導致程序的運行不可預料,這時我們就需要規范對共享狀態的修改
1.假設使用的是一個共享狀態 appState,每個人都可以修改它
2.所有對共享狀態的操作都是不可預料的(某個模塊 appState.title = null 你一點意見都沒有),出現問題的時候 debug 起來就非常困難
3.如果所有對數據的操作必須通過 dispatch 函數。它接受一個參數 action,這個 action 是一個普通的 JavaScript 對象,里面必須包含一個 type 字段來聲明你到底想干什么,那就好操作了
4.我們可以把appState 和 dispatch抽離出來結合到一起形成store,構建一個函數 createStore,用來專門生產這種 state 和 dispatch 的集合
/*createStore 接受兩個參數,一個是表示應用程序狀態的 state;另外一個是 stateChanger, 它來描述應用程序狀態會根據 action 發生什么變化*/ function createStore (state, stateChanger) { const getState = () => state const dispatch = (action) => stateChanger(state, action) return { getState, dispatch } } renderApp(store.getState()) // 首次渲染頁面 store.dispatch({ type: "UPDATE_TITLE_TEXT", text: "《React.js 》" }) // 修改標題文本 store.dispatch({ type: "UPDATE_TITLE_COLOR", color: "blue" }) // 修改標題顏色 renderApp(store.getState()) // 把新的數據渲染到頁面上3.實現Redux數據刷新優化
1.細上面代碼中更改數據后需要手動調用renderapp刷新,這里我們可以用觀察者模式優化刷新
function createStore (state, stateChanger) { const listeners = [] const subscribe = (listener) => listeners.push(listener) const getState = () => state const dispatch = (action) => { stateChanger(state, action) listeners.forEach((listener) => listener())//dispatch后調用我傳入subscribe的刷新方式 } return { getState, dispatch, subscribe } } //可以用同一個APPstate去渲染不同的頁面 const store = createStore(appState, stateChanger) store.subscribe(() => renderApp(store.getState())) store.subscribe(() => renderApp2(store.getState())) store.subscribe(() => renderApp3(store.getState()))
2.避免重復渲染優化 這里直接引用胡子大叔的優化
function createStore (state, stateChanger) { const listeners = [] const subscribe = (listener) => listeners.push(listener) const getState = () => state const dispatch = (action) => { state = stateChanger(state, action) // 覆蓋原對象 listeners.forEach((listener) => listener()) } return { getState, dispatch, subscribe } } function renderApp (newAppState, oldAppState = {}) { // 防止 oldAppState 沒有傳入,所以加了默認參數 oldAppState = {} if (newAppState === oldAppState) return // 數據沒有變化就不渲染了 console.log("render app...") renderTitle(newAppState.title, oldAppState.title) renderContent(newAppState.content, oldAppState.content) } function renderTitle (newTitle, oldTitle = {}) { if (newTitle === oldTitle) return // 數據沒有變化就不渲染了 console.log("render title...") const titleDOM = document.getElementById("title") titleDOM.innerHTML = newTitle.text titleDOM.style.color = newTitle.color } function renderContent (newContent, oldContent = {}) { if (newContent === oldContent) return // 數據沒有變化就不渲染了 console.log("render content...") const contentDOM = document.getElementById("content") contentDOM.innerHTML = newContent.text contentDOM.style.color = newContent.color } let appState = { title: { text: "React.js 小書", color: "red", }, content: { text: "React.js 小書內容", color: "blue" } } function stateChanger (state, action) { switch (action.type) { case "UPDATE_TITLE_TEXT": return { // 構建新的對象并且返回 ...state, title: { ...state.title, text: action.text } } case "UPDATE_TITLE_COLOR": return { // 構建新的對象并且返回 ...state, title: { ...state.title, color: action.color } } default: return state // 沒有修改,返回原來的對象 } } const store = createStore(appState, stateChanger) let oldState = store.getState() // 緩存舊的 state store.subscribe(() => { const newState = store.getState() // 數據可能變化,獲取新的 state renderApp(newState, oldState) // 把新舊的 state 傳進去渲染 oldState = newState // 渲染完以后,新的 newState 變成了舊的 oldState,等待下一次數據變化重新渲染 }) renderApp(store.getState()) // 首次渲染頁面 store.dispatch({ type: "UPDATE_TITLE_TEXT", text: "《React.js 小書》" }) // 修改標題文本 store.dispatch({ type: "UPDATE_TITLE_COLOR", color: "blue" }) // 修改標題顏色4.Reducer
其實 appState 和 stateChanger 可以合并到一起去
1將appstate放入statechanger
function stateChanger (state, action) { if (!state) { return { title: { text: "React.js 小書", color: "red", }, content: { text: "React.js 小書內容", color: "blue" } } } switch (action.type) { case "UPDATE_TITLE_TEXT": return { ...state, title: { ...state.title, text: action.text } } case "UPDATE_TITLE_COLOR": return { ...state, title: { ...state.title, color: action.color } } default: return state } }
2.creactstore的參數就會被優化為一個
function createStore (stateChanger) { let state = null const listeners = [] const subscribe = (listener) => listeners.push(listener) const getState = () => state const dispatch = (action) => { state = stateChanger(state, action) listeners.forEach((listener) => listener()) } dispatch({}) // 初始化 state return { getState, dispatch, subscribe } }
3.最后我們規定createStore參數的名字為reducer,且reducer是一個純函數
reducer 是不允許有副作用的。你不能在里面操作 DOM,也不能發 Ajax 請求,更不能直接修改 state,它要做的僅僅是 —— 初始化和計算新的 state
// 定一個 reducer function reducer (state, action) { /* 初始化 state 和 switch case */ } // 生成 store const store = createStore(reducer) // 監聽數據變化重新渲染頁面 store.subscribe(() => renderApp(store.getState())) // 首次渲染頁面 renderApp(store.getState()) // 后面可以隨意 dispatch 了,頁面自動更新 store.dispatch(...)5.React-redux中的store和context
React.js 的 context 中提出,我們可用把共享狀態放到父組件的 context 上,這個父組件下所有的組件都可以從 context 中直接獲取到狀態而不需要一層層地進行傳遞了,但組件對其的改動會讓context不可預料。 store 的數據不是誰都能修改,而是約定只能通過 dispatch 來進行修改,這樣的話每個組件既可以去 context 里面獲取 store 從而獲取狀態,又不用擔心它們亂改數據了,所以將store和context結合起來
1.構建自己的React-redux
import React, { Component } from "react" import PropTypes from "prop-types" import ReactDOM from "react-dom" import Header from "./Header" import Content from "./Content" import "./index.css" function createStore (reducer) { let state = null const listeners = [ const subscribe = (listener) => listeners.push(listener) const getState = () => state//這是函數表達式 調用它時state已經初始化了 const dispatch = (action) => { state = reducer(state, action) listeners.forEach((listener) => listener()) } dispatch({}) // 初始化 state return { getState, dispatch, subscribe } } const themeReducer = (state, action) => { if (!state) return { themeColor: "red" } switch (action.type) { case "CHANGE_COLOR": return { ...state, themeColor: action.themeColor } default: return state } } const store = createStore(themeReducer) class Index extends Component { static childContextTypes = { store: PropTypes.object } getChildContext () { return { store }//將store放入context } render () { return () } }
2.子組件獲取context中的配置
class Header extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { themeColor: "" } } componentWillMount () { this._updateThemeColor() } _updateThemeColor () { const { store } = this.context//解構賦值取出來 const state = store.getState() this.setState({ themeColor: state.themeColor })//放到state中來用 } render () { return (React.js 小書
) } }
3.用dispatch去改變配置刷新頁面
//首先配置監聽函數的刷新模式 componentWillMount () { const { store } = this.context this._updateThemeColor()//獲取默認數據加載 store.subscribe(() => this._updateThemeColor())//dispatch數據更改后加載 } //觸發事件 handleSwitchColor (color) { const { store } = this.context store.dispatch({ type: "CHANGE_COLOR", themeColor: color }) }6.React-redux與組件拆分開,讓組件無污染可復用性強
可以把一些可復用的邏輯放在高階組件當中,高階組件包裝的新組件和原來組件之間通過 props 傳遞信息,減少代碼的重復程度,我們需要高階組件幫助我們從 context 取數據,然后用高階組件把它們包裝一層,高階組件和 context 打交道,把里面數據取出來通過 props 傳給 Dumb 組件
1.這個高階組件起名字叫 connect,因為它把 Dumb 組件和 context 連接
2.每個傳進去的組件需要 store 里面的數據都不一樣的,所以除了給高階組件傳入 Dumb 組件以外,還需要告訴高級組件我們需要什么數據
import React, { Component } from "react" import PropTypes from "prop-types" //connect 現在是接受一個參數 mapStateToProps,然后返回一個函數,這個返回的函數才是高階組件 export const connect = (mapStateToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } render () { const { store } = this.context let stateProps = mapStateToProps(store.getState()) // {...stateProps} 意思是把這個對象里面的屬性全部通過 `props` 方式傳遞進去 return} } return Connect } ---------- //mapStateToProps為傳入數據的方式 const mapStateToProps = (state) => { return { themeColor: state.themeColor } } Header = connect(mapStateToProps)(Header) //這里的mapStateToprops在connect里面執行并把獲取的數據放到header的props中
3.除了傳遞數據我們還需要高階組件來 dispatch
const mapDispatchToProps = (dispatch) => { return { onSwitchColor: (color) => { dispatch({ type: "CHANGE_COLOR", themeColor: color }) } } }
4.結合起來構建Connect
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { allProps: {} } } componentWillMount () { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps () { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {} // 防止 mapStateToProps 沒有傳入 let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {} // 防止 mapDispatchToProps 沒有傳入 this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render () { return} } return Connect }
5.剝離出index.js
class Index extends Component { static childContextTypes = { store: PropTypes.object } getChildContext () { return { store } }//這些都是污染需要剝離 render () { return () } }
6.Provider
//將index中污染部分放入Provider,再用成為index的父組件 export class Provider extends Component { static propTypes = { store: PropTypes.object, children: PropTypes.any } static childContextTypes = { store: PropTypes.object } getChildContext () { return { store: this.props.store } } render () { return ({this.props.children}) } }
... // 頭部引入 Provider import { Provider } from "./react-redux" ... // 刪除 Index 里面所有關于 context 的代碼 class Index extends Component { render () { return () } } // 把 Provider 作為組件樹的根節點 ReactDOM.render(, document.getElementById("root") )
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/108899.html
摘要:寫在前頭簡介隨著單頁應用開發日趨復雜,需要管理比任何時候都要多的狀態。如果一個的變化會引起另一個變化,那么當變化時,就可能引起對應以及另一個的變化,依次地,可能會引起另一個的變化。一些庫如試圖在視圖層禁止異步和直接操作來解決這個問題。 寫在前頭 redux 簡介 ? 隨著 JavaScript 單頁應用開發日趨復雜,JavaScript 需要管理比任何時候都要多的 state (...
摘要:要實現先搞清楚的就是這么一個東西,某個組件只要往自己的里面放了某些狀態,這個組件之下的所有子組件都直接訪問這個狀態而不需要通過中間組件的傳遞。 1.要實現redux,先搞清楚context React.js 的 context 就是這么一個東西,某個組件只要往自己的 context 里面放了某些狀態,這個組件之下的所有子組件都直接訪問這個狀態而不需要通過中間組件的傳遞。一個組件的 co...
摘要:日常項目直接使用是完全沒有問題的,可是隨著項目的日益壯大,組件數量的逐漸增長,組件之間的嵌套使得數據的管理越來越繁重。最后數據保存進了中的,頁面也會根據的改變自動更新。 以下文章均為個人近期所學心得,自學react、redux,逐漸找到自己的方向,現將自己的方向方式寫出來,以供大家學習參考,肯定會有不足,歡迎批評指正。 日常項目直接使用react是完全沒有問題的,可是隨著項目的日益壯大...
摘要:但這并不是最佳的方式。最佳的方式是使用提供的和方法。也就是說,與的與完全無關。另外,如果使用對做屬性類型檢查,方法和方法為添加的屬性是存在的。注意,的變化不會引起上述過程,默認在組件的生命周期中是固定的。 轉載注: 本文作者是淘寶前端團隊的葉齋。筆者非常喜歡這篇文章,故重新排版并轉載到這里,同時也加入了一些自己的體會。 原文地址:http://taobaofed.org/blog/...
閱讀 976·2023-04-26 02:56
閱讀 9531·2021-11-23 09:51
閱讀 1883·2021-09-26 10:14
閱讀 2987·2019-08-29 13:09
閱讀 2159·2019-08-26 13:29
閱讀 576·2019-08-26 12:02
閱讀 3569·2019-08-26 10:42
閱讀 3008·2019-08-23 18:18