React个人入门总结《五》
簡介
這一次總結(jié)的是 React-redux 的實現(xiàn),可以參考一下 大佬的文章 。
首先要知道 redux 的基本使用:
創(chuàng)建一個 Store 。
<!-- store --> function createStore (reducer) {let state = nullconst listeners = []const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe } }<!-- reducer --> const themeReducer = (state = {}, action) => {switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state} }<!-- 創(chuàng)建 store --> const store = createStore(themeReducer) 復(fù)制代碼Store 是保存數(shù)據(jù)的地方,整個應(yīng)用只有一個,調(diào)用 CreateStore 函數(shù)并且傳入一個 Reducer 來創(chuàng)建一個 Store,并且會返回新的 Store 對象。
獲取當(dāng)前的 State。
<!-- 調(diào)用 store.getState 獲取當(dāng)前的狀態(tài) --> const state = store.getState() 復(fù)制代碼State 是 Store 里面包含的數(shù)據(jù)對象,可以通過 Store.getState() 獲取
通過 Dispatch 發(fā)送 Action 改變 State。
<!-- 調(diào)用 dispatch 發(fā)送 action --> store.dispatch({type: 'CHANGE_COLOR',themeColor: 'blue' }) 復(fù)制代碼Action 就是 View 發(fā)出的通知,表示 View 要變化,其中 Type 是必須的,其余可以 自定義 。
如果要寫多個 Action 覺得麻煩,可以使用 Action Creator 函數(shù)來生產(chǎn) Action 。
function updateThemeColor (action) {type: action.type,themeColor: action.themeColor }store.dispatch( updateThemeColor({ type: 'CHANGE_COLOR', themeColor: 'blue' }) ) 復(fù)制代碼Reducer 是 Store 收到 Action 之后用來計算 State 并且返回新的 State,也就是說必須要有 Return 。
<!-- reducer --><!-- 初始 state 是必須的,redux 規(guī)定不能為 undefined 和 null --> const themeReducer = (state = {}, action) => {switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state} } 復(fù)制代碼Reducer 可以根據(jù)不同的 Type 來進(jìn)行不同的邏輯處理,并且每次都會返回新的 state 來覆蓋原來的 state 。
Reducer 是一個純函數(shù),同樣的輸入就會得到同樣的輸出。
Reducer 必須要返回一個新的狀態(tài),而不是改變原有的狀態(tài),請參考下面寫法:
// State 是一個對象 function reducer(state, action) {return Object.assign({}, state, { thingToChange });// 或者return { ...state, ...newState }; }// State 是一個數(shù)組 function reducer(state, action) {return [...state, newItem]; } 復(fù)制代碼調(diào)用 subscribe 傳入一個函數(shù),狀態(tài)改變時會調(diào)用此函數(shù)。
store.subscribe(()=> {ReactDOM.render() }) 復(fù)制代碼Store.subscribe 方法設(shè)置監(jiān)聽函數(shù),一旦 State 發(fā)生變化,就自動執(zhí)行這個函數(shù)。
一般傳入 render 和 this.setState() 來監(jiān)聽頁面重新渲染。
調(diào)用此方法會返回一個函數(shù),調(diào)用函數(shù)之后可以解除監(jiān)聽。
React-redux
首先之前向組件傳遞參數(shù)時,第一次使用的是 狀態(tài)提升,即通過父級傳入一個函數(shù)然后拿到組件里面的東西,再傳入另一個組件里面。
當(dāng)嵌套的太多層時,使用 狀態(tài)提升 會非常麻煩,然后第二次就開始使用了 Context ,由于 Context 能隨意被改變,這時我們可以把 Context 和 Store 結(jié)合使用,這樣就不能隨意的改變 Context ,并且狀態(tài)還能共享。
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; // 引入組件 import { Header } from './component/Header'; import { Content } from './component/Content'; import * as serviceWorker from './serviceWorker'; <!-- store --> function createStore(reducer) {let state = null;const listeners = [];const subscribe = (listener) => listeners.push(listener);const getState = () => state;const dispatch = (action) => {state = reducer(state, action);listeners.forEach((listener) => listener());}dispatch({});return { getState, dispatch, subscribe }; } <!-- reducer --> const themeReducer = (state, action) => {if (!state) return {themeColor: 'red'}switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state} } <!-- 創(chuàng)建 store --> const store = createStore(themeReducer);class Index extends Component {<!-- 設(shè)置子組件的 contextType -->static childContextTypes = {store: PropTypes.object}<!-- 設(shè)置 context -->getChildContext() {return { store }}render() {return (<div className="index"><Header /><Content /></div>)} }ReactDOM.render(<Index />, document.getElementById('root')); 復(fù)制代碼創(chuàng)建 store 然后把它放到 context 里面,這樣所有子組件都可以拿到了。
<!-- Header --> import React, { Component } from 'react'; import PropTypes from 'prop-types';class Header extends Component {static contextTypes = {store: PropTypes.object }constructor(props) {super(props);this.state = {themeColor: ''}}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><h1 style={{color: this.state.themeColor}}>is header</h1></div>)} } export { Header };<!-- Content--> import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { ThemeSwitch } from './ThemeSwitch';class Content extends Component {static contextTypes = {store: PropTypes.object}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><h2 style={{ color: this.state.themeColor }}>is Content</h2><ThemeSwitch /></div>)} } export { Content };<!-- ThemeSwitch --> import React, { Component } from 'react'; import PropTypes from 'prop-types';class ThemeSwitch extends Component {static contextTypes = {store: PropTypes.object}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><button style={{ color: this.state.themeColor }}>red</button><button style={{ color: this.state.themeColor }}>blue</button></div>)} }export { ThemeSwitch }; 復(fù)制代碼上面三個子組件使用 store.getState() 獲取到 reducer 設(shè)置的默認(rèn)狀態(tài),這樣的話就可以實現(xiàn)共享狀態(tài)了。
接下來我們實現(xiàn)點擊按鈕改變顏色:
<!-- ThemeSwitch --> updateThemeColor(color) {let { store } = this.context;store.dispatch({type: 'CHANGE_COLOR',themeColor: color}) }render() {return (<div className="header"><button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button><button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button></div>) } 復(fù)制代碼調(diào)用 dispatch 然后傳入 action ,然后會調(diào)用 reducer 函數(shù),然后根據(jù)傳入的 action.type 改變狀態(tài),之后再返回一個新的狀態(tài)。
返回新狀態(tài)時要想監(jiān)聽頁面的更新,可以在 subscribe 傳入要監(jiān)聽的函數(shù),這樣就可以在調(diào)用 dispatch 同時會調(diào)用你傳入的函數(shù),然后再一次調(diào)用 this.setState 觸發(fā)頁面重新渲染。
_updateThemeColor() {<!-- 重新獲取一次狀態(tài) -->let state = this.context.store.getState();<!-- 重新設(shè)置,并且觸發(fā)重新渲染 -->this.setState({themeColor: state.themeColor})}componentWillMount() {let { store } = this.context;<!-- 首次渲染 -->this._updateThemeColor();store.subscribe(() => this._updateThemeColor())} 復(fù)制代碼connect
上面的組件有著重復(fù)的邏輯,首先取出 store 的 state 然后設(shè)置成自己的狀態(tài),還有一個就是對 context 依賴過強,這時我們可以利用 高階組件 來和 context 打交道,這時就不用每個組件都獲取一遍 store 了。
import React, { Component } from 'react'; import PropTypes from 'prop-types'; <!-- 接受一個組件 --> const connect = (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}render() {const { store } = this.context;return <WrappedComponent />}}return Connect; } export { connect }; 復(fù)制代碼connect 是用于從 UI 組件生成 容器組件 ,也就是說我們傳入的組件只是負(fù)責(zé)呈現(xiàn)和展示,而 容器組件 負(fù)責(zé)業(yè)務(wù)邏輯和帶有內(nèi)部狀態(tài),connect 負(fù)責(zé)的是將兩者合并起來,生成并返回新的組件。
由于每個傳進(jìn)去的組件需要的 store 里面的數(shù)據(jù)都不一樣,所以我們還要傳入一個函數(shù)來告訴 高階組件 正確獲取數(shù)據(jù)。
- mapStateToProps
mapStateToProps 是一個獲取 store 保存的狀態(tài),然后將這個狀態(tài)轉(zhuǎn)化為 UI組件 的數(shù)據(jù)的函數(shù),它必須要返回一個對象,而這個對象用來進(jìn)行狀態(tài)轉(zhuǎn)化的。
import React, { Component } from 'react'; import PropTypes from 'prop-types';const connect = (mapStateToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}render () {const { store } = this.context;<!-- 從store獲取state并且轉(zhuǎn)化之后全部傳入 props -->let stateProps = mapStateToProps(store.getState());return <WrappedComponent {...stateProps} />}}return Connect; }export { connect }; 復(fù)制代碼上面最關(guān)鍵的一步就是調(diào)用 mapStateToProps 時,從 store 獲取到 state 之后然后傳入到 mapStateToProps 函數(shù)中,然后這函數(shù)會返回一個轉(zhuǎn)化后的 state ,然后把這些轉(zhuǎn)化的狀態(tài)全部傳入 props 里面。
可以看出 connect 把 Dumb組件(純組件) 和 context 連起來了,下面只需要調(diào)用 connect 然后傳入一個 mapStateToProps 和 UI組件 就可以使用了。
<!-- Header --> import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from './React-redux';class Header extends Component {static propTypes = {themeColor: PropTypes.string}render() {return (<div className="header"><h1 style={{color: this.props.themeColor}}>is header</h1></div>)} } const mapStateToProps = (state) => {return {themeColor: state.themeColor} }Header = connect(mapStateToProps)(Header) export { Header };<!-- Content --> import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from './React-redux'; import { ThemeSwitch } from './ThemeSwitch';class Content extends Component {static propTypes = {themeColor: PropTypes.string}render() {return (<div className="header"><h2 style={{ color: this.props.themeColor }}>is Content</h2><ThemeSwitch /></div>)} }const mapStateToProps = (state) => {return {themeColor: state.themeColor} }Content = connect(mapStateToProps)(Content) export { Content }; 復(fù)制代碼由于 mapStateToProps 返回的對象經(jīng)過 connect 傳入組件的 props 中,我們直接可以用 this.props 直接獲取到。
接著把 connect 的代碼復(fù)制到一個叫 React-redux 的文件,然后可以刪掉之前那些引入 store 的代碼了。
現(xiàn)在點擊按鈕只有按鈕會變顏色,接下來我們修改一下 connect :
import React, { Component } from 'react'; import PropTypes from 'prop-types';const connect = (mapStateToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}constructor (props) {super(props);this.state = { allProps: {}}}componentWillMount () {const { store } = this.context;this._updateProps();store.subscribe(() => this._updateProps())}_updateProps () {const { store } = this.context<!-- 現(xiàn)在 mapStateToProps 可以接受兩個參數(shù) -->let stateProps = mapStateToProps(store.getState(), this.props)<!-- 整合普通的 props 和從 state 生成的 props -->this.setState({allProps: {...stateProps,...this.props}})}render () {return <WrappedComponent { ...this.state.allProps } />}}return Connect; } export { connect }; 復(fù)制代碼每次點擊按鈕調(diào)用 dispatch 都會把心的 state 設(shè)置到自己的 state 之后,然后返回給組件,這樣組件之前的 props 也會保留,同時 mapStateToProps 可以接受第二個參數(shù),這個參數(shù)為當(dāng)前 UI組件 的 props 。
<!-- 第一個為 store 獲取到的 state , 第二個為當(dāng)前 ui 組件的 props (不是最新的) --> const mapStateToProps = (state, props) => {console.log(state, props)return {themeColor: state.themeColor} } 復(fù)制代碼使用 props 作為參數(shù)后,如果容器組件的參數(shù)發(fā)生變化,也會引發(fā) UI組件 重新渲染,connect 方法可以省略 mapStateToProps 參數(shù),這樣 store 的更新不會引起組件的更新。
- mapDispatchToProps
mapDispatchToProps 是 connect 的第二個參數(shù),用來建立 UI組件 的參數(shù)到 store.dispatch 方法的映射,它作為函數(shù)時可以接受兩個參數(shù),一個是 dispatch ,一個則是 UI組件 的 props 。
mapDispatchToProps 可以定義 action 然后傳給 store 。
import React, { Component } from 'react'; import PropTypes from 'prop-types';const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}constructor (props) {super(props);this.state = { allProps: {}}}componentWillMount () {const { store } = this.context;this._updateProps();store.subscribe(() => this._updateProps())}_updateProps () {const { store } = this.contextlet stateProps = mapStateToProps? mapStateToProps(store.getState(), this.props): {}let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props): {}this.setState({allProps: {...stateProps,...dispatchProps,...this.props}})}render () {return <WrappedComponent { ...this.state.allProps } />}}return Connect; } export { connect }; 復(fù)制代碼接受 mapDispatchToProps 作第二個參數(shù),調(diào)用時把 dispatch 和 props 傳進(jìn)去,返回 onClickUpdate 然后直接傳入 props 中返回給 UI組件 ,接著我們可以直接調(diào)用 this.props.onClickUpdate 然后調(diào)用 dispatch 來更新狀態(tài)。
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from './React-redux';class ThemeSwitch extends Component {static contextTypes = {onClickUpdate: PropTypes.func}<!-- 點擊調(diào)用 onClickUpdate -->updateThemeColor(color) {if(this.props.onClickUpdate) {this.props.onClickUpdate(color)}}render() {return (<div className="header"> <button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button><button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button></div>)} } <!-- 在真正的 react-redux 不一定是函數(shù) --> const mapStateToProps = (state, props) => {return {themeColor: state.themeColor} } <!-- 在真正的 react-redux 可以是一個對象 --> const mapDispatchToProps = (dispatch, props) => {return {onClickUpdate: (color) => {dispatch({type: 'CHANGE_COLOR',themeColor: color})}} }ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch); export { ThemeSwitch }; 復(fù)制代碼這樣點擊按鈕之后又可以改變顏色了。
Provider
connect 方法生成容器后需要拿到 state 對象,目前咱們能拿到 store 是因為在 index.js 中設(shè)置了 context ,這樣會直接污染 index.js , React-redux 提供了 Provider 來充當(dāng)最外層容器,這樣就不需要在 index 設(shè)置 context 了。
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>)} }export { Provider }; 復(fù)制代碼在 React-redux 的文件增加上面的代碼,其實也就是另外設(shè)置一個容器來替代之前 index.js 干的活,這里返回了 this.props.children ,也說明要用這個組件把其他的組件包起來。
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import { Provider } from './component/React-redux'; // 引入組件 import { Header } from './component/Header'; import { Content } from './component/Content'; import * as serviceWorker from './serviceWorker';function createStore(reducer) {let state = null;const listeners = [];const subscribe = (listener) => listeners.push(listener);const getState = () => state;const dispatch = (action) => {state = reducer(state, action);listeners.forEach((listener) => listener());}dispatch({});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 {render() {return (<div className="index"><Header /><Content /></div>)} }ReactDOM.render(<!-- 把 store 和 外層組件包起來 --><Provider store= { store }><Index /></Provider>, document.getElementById('root') ); 復(fù)制代碼把 Store 傳入給 Provider ,然后它把 store 設(shè)置成 context ,這樣其他子組件都能拿到 store ,并且把最外層容器包起來,然后使用 this.props.children 全部羅列出來。
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from './React-redux';class Header extends Component {<!-- 別忘了聲明這玩意,不然拿不到 -->static contextTypes = {store: PropTypes.object}static propTypes = {themeColor: PropTypes.string}componentWillMount() {console.log(this.context)}render() {return (<div className="header"><h1 style={{color: this.props.themeColor}}>is header</h1></div>)} } const mapStateToProps = (state, props) => {return {themeColor: state.themeColor} }Header = connect(mapStateToProps)(Header) export { Header }; 復(fù)制代碼拿到之后接下來就可以浪了,可以在當(dāng)前組件調(diào)用里面的方法,非常靈活。
總結(jié)
上一篇 --- React個人入門總結(jié)《四》
下一篇 --- React個人入門總結(jié)《六》
總結(jié)
以上是生活随笔為你收集整理的React个人入门总结《五》的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Haproxy+Rabbitmq中的问题
- 下一篇: idea加载lombok插件