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