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 ( <div> <Header /> <Main /> </div> ) } }
//子組件直接獲取context裏面的東西 //子組件要獲取 context 裏面的內容的話,就必須寫 contextTypes 來聲明和驗證你須要獲取的狀態的類型, //它也是必寫的 class Title extends Component { static contextTypes = { themeColor: PropTypes.string } render () { return ( <h1 style={{ color: this.context.themeColor }}>React.js</h1> ) } }
context 打破了組件和組件之間經過 props 傳遞數據的規範,極大地加強了組件之間的耦合性。並且,就如全局變量同樣,context 裏面的數據能被隨意接觸就能被隨意修改,每一個組件都可以改 context 裏面的內容會致使程序的運行不可預料,這時咱們就須要規範對共享狀態的修改
1.假設使用的是一個共享狀態 appState,每一個人均可以修改它
2.全部對共享狀態的操做都是不可預料的(某個模塊 appState.title = null 你一點意見都沒有),出現問題的時候 debug 起來就很是困難
3.若是全部對數據的操做必須經過 dispatch 函數。它接受一個參數 action,這個 action 是一個普通的 JavaScript 對象,裏面必須包含一個 type 字段來聲明你到底想幹什麼,那就好操做了
4.咱們能夠把appState 和 dispatch抽離出來結合到一塊兒造成store,構建一個函數 createStore,用來專門生產這種 state 和 dispatch 的集合css
/*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()) // 把新的數據渲染到頁面上
1.細上面代碼中更改數據後須要手動調用renderapp刷新,這裏咱們能夠用觀察者模式優化刷新react
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.避免重複渲染優化 這裏直接引用鬍子大叔的優化redux
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' }) // 修改標題顏色
其實 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的參數就會被優化爲一個app
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是一個純函數dom
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(...)
React.js 的 context 中提出,咱們可用把共享狀態放到父組件的 context 上,這個父組件下全部的組件均可以從 context 中直接獲取到狀態而不須要一層層地進行傳遞了,但組件對其的改動會讓context不可預料。 store 的數據不是誰都能修改,而是約定只能經過 dispatch 來進行修改,這樣的話每一個組件既能夠去 context 裏面獲取 store 從而獲取狀態,又不用擔憂它們亂改數據了,因此將store和context結合起來
1.構建本身的React-reduxide
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 ( <div> <Header /> <Content /> </div> ) } }
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 ( <h1 style={{ color: this.state.themeColor }}>React.js 小書</h1> ) } }
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 }) }
能夠把一些可複用的邏輯放在高階組件當中,高階組件包裝的新組件和原來組件之間經過 props 傳遞信息,減小代碼的重複程度,咱們須要高階組件幫助咱們從 context 取數據,而後用高階組件把它們包裝一層,高階組件和 context 打交道,把裏面數據取出來經過 props 傳給 Dumb 組件
1.這個高階組件起名字叫 connect,由於它把 Dumb 組件和 context 鏈接
2.每一個傳進去的組件須要 store 裏面的數據都不同的,因此除了給高階組件傳入 Dumb 組件之外,還須要告訴高級組件咱們須要什麼數據this
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 <WrappedComponent {...stateProps} /> } } 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 <WrappedComponent {...this.state.allProps} /> } } return Connect }
5.剝離出index.js
class Index extends Component { static childContextTypes = { store: PropTypes.object } getChildContext () { return { store } }//這些都是污染須要剝離 render () { return ( <div> <Header /> <Content /> </div> ) } }
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 ( <div>{this.props.children}</div> ) } }
... // 頭部引入 Provider import { Provider } from './react-redux' ... // 刪除 Index 裏面全部關於 context 的代碼 class Index extends Component { render () { return ( <div> <Header /> <Content /> </div> ) } } // 把 Provider 做爲組件樹的根節點 ReactDOM.render( <Provider store={store}> <Index /> </Provider>, document.getElementById('root') )