redux和react-redux的區別及用法


筆記,參考程墨老師的《深入淺出React和Redux》。閱讀之前可以先了解flux框架

Redux框架

image.png

Redux原則

Flux的基本原則是“單向數據流”, Redux在此基礎上強調三個基本原則:

  • 唯一數據源

    唯一數據源指的是應用的狀態數據應該只存儲在唯一的一個Store上。這個唯一Store上的狀態,是一個樹形的對象,每個組件往往只是用樹形對象上一部分的數據,而如何設計Store上狀態的結構,就是Redux應用的核心問題

  • 保持狀態只讀

    保持狀態只讀,就是說不能去直接修改狀態,要修改Store的狀態,必須要通過派發一個action對象完成。改變狀態的方法不是去修改狀態上值,而是創建一個新的狀態對象返回給Redux,由Redux完成新的狀態的組裝。

  • 數據改變只能通過純函數完成

    這里所說的純函數就是Reducer, Redux這個名字的前三個字母Red代表的就是Reducer。按照創作者Dan Abramov的說法,Redux名字的含義是Reducer+Flux

    reducer(state,action)
    

    第一個參數state是當前的狀態,第二個參數action是接收到的action對象,而reducer函數要做的事情,就是根據state和action的值產生一個新的對象返回,注意reducer必須是純函數,也就是說函數的返回結果必須完全由參數state和action決定,而且不產生任何副作用,也不能修改參數state和action對象

    function reducer (state,action)=>{
        const {counterCaption}=action
        switch(action.type){
            case ActionTypes.INCREMENT:
                return{...state,[counterCaption]:state[counterCaption]+1}
            case ActionTypes.DECREMENT:
                return{...state,[counterCaption]:state[counterCaption]-1}
            default:
            	return state
        }
    }
    

Redux示例

示例

基本目錄如下:

image.png

效果:

示例源碼




安裝

npm i --save redux

Action

和Flux一樣,Redux應用習慣上把action類型和action構造函數分成兩個文件定義

src/ActionTypes.js:

export const INCREMENT='increment'
export const DECREMENT='decrement'

src/Actions.js

import * as ActionTypes from './ActionTypes.js';

export const increament=(counterCaption)=>{
    return {
        type:ActionTypes.INCREMENT,
        counterCaption:counterCaption
    }
}
export const decrement=(counterCaption)=>{
    return {
        type:ActionTypes.DECREMENT,
        counterCaption:counterCaption
    }
}

對比發現Redux中每個action構造函數都返回一個action對象。而Flux版本中action構造函數則直接調用Dispatcher的dispatch函數把action對象派發出去。即redux與flux的區別在於,在actions文件中,redux不負責派發

//flux版本
import * as AcitonTypes from './ActionTypes';
import AppDispatcher from './AppDispatcher'

export const increment = (counterCaption)=>{
 AppDispatcher.dispatch({
     type:AcitonTypes.INCREMENT,
     counterCaption:counterCaption
 })
}
export const decrement = (counterCaption)=>{
 AppDispatcher.dispatch({
     type:AcitonTypes.DECREMENT,
     counterCaption:counterCaption
 })
}

Store

Redux庫提供的createStore函數:第一個參數代表更新狀態的reducer,第二個參數是狀態的初始值,第三個參數可選,代表StoreEnhancer。狀態通過getStore()獲取

src/Store.js

import {createStore} from 'redux'
import reducer from './Reducer.js'
const initValues={
    'First':0,
    'Second':10,
    'Third':20
}
const store=createStore(reducer,initValues)
export default store

Reducer

用於更新狀態。任何通過dispatch派發的action都會通過這里來改變store狀態

src/Reducer.js

import * as ActionTypes from './ActionTypes.js'
export default (state,action)=>{
    const {counterCaption}=action
    switch(action.type){
        case ActionTypes.INCREMENT:
            return{...state,[counterCaption]:state[counterCaption]+1}
        case ActionTypes.DECREMENT:
            return{...state,[counterCaption]:state[counterCaption]-1}
        default:
        	return state
    }
}

對比發現:Redux比Flux多了一個參數state,而在Flux中沒有state參數。即redux與flux的區別在於,Flux中的state是由Store管理的,而Redux中的state由Redux本身管理。

//flux版本
CounterStore.dispatchToken=AppDispatcher.register((action)=>{
 if(action.type===AcitonTypes.INCREMENT){
     counterValues[action.counterCaption]++
     CounterStore.emitChange()
 }else if(action.type===AcitonTypes.DECREMENT){
     counterValues[action.counterCaption]--
     CounterStore.emitChange()
 }
})

View

src/views/ControlPanel.js

import React from 'react'
import Counter from './Counter'
import Summary from './Summary'

export default class ControlPanel extends React.Component{
    render(){
        return(
        	<div>
            	<Counter caption="First" />
                <Counter caption="Second" />
                <Counter caption="Third" />
                <hr/>
                <Summary/>
            </div>
        )
    }
}

src/views/Counter.js

import React from 'react'
//1. 引入store和action
import store from '../Store.js'
import * as Actions from '../Actions'

export default class Counter extends React.Component{
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
        this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
        this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
        //3. 初始化狀態
        this.state = this.getOwnState()
    }

    //2. 返回一個從store中獲得的相對應的狀態
    getOwnState(){
        return {value:store.getState()[this.props.caption]}
    }
    
    //4. 保持store上狀態和this.state的同步,只要store改變就調用onChange
    //   subscribe和unsubscribe分別用於注冊與注銷監聽
    componentDidMount(){
        store.subscribe(this.onChange)
    }
    componentWillUnmount(){
        store.unsubscribe(this.onChange)
    }   
    onChange(){
        this.setState(this.getOwnState())
    }

    //5.  改變store狀態唯一的方法是派發action。調用store.dispatch函數派發action
    onClickIncrementButton(){
        store.dispatch(Actions.increment(this.props.caption))
    }
    onClickDecrementButton(){
        store.dispatch(Actions.decrement(this.props.caption))
    }
    
    render(){
        const {caption} =this.props
        const value=this.state.value
        return(
        	<div>
            	<input type="button" onClick={this.onClickIncrementButton} value="+"/>
                <input type="button" onClick={this.onClickDecrementButton} value="-"/>
                <span>{caption} count:{value}</span>
            </div>
        )
    }
}

src/views/Summary.js

import React from 'react'
import store from '../Store.js'

export default class Summary extends React.Component{
    constructor(props){
        super(props)
        this.onUpdate = this.onUpdate.bind(this);
        this.state=this.getOwnState.bind(this);
    }
    
    //1.定義求和函數,返回對象
    getOwnState(){
        const state=store.getState();
        let sum=0
        for(const key in state){
            if(state.hasOwnProperty(key)){
                sum+=state[key]
            }
        }
        return {sum:sum}
    }
    
    //2. 同步狀態
    componentDidMount(){
        this.setState(this.getOwnState())
        store.subscribe(this.onUpdate)
    }
    componentWillUnmount(){
        store.unsubscribe(this.onUpdate)
    }    
    onUpdate() {
        this.setState(this.getOwnState())
    }
    
    render(){
        return(
        	<div>
            	total:{this.state.sum}
            </div>
        )
    }
}

總結

  • 定義action類型
  • 創建action構造函數
  • 用createStore創建store:包含更新狀態reducer、初始狀態、(StoreEnhancer可選)
  • 創建reducer:包含state和ation參數
  • view部分初始化狀態:通過store.getStore()獲取狀態
  • 通過subscribe和unsubscribe同步狀態
  • 通過store.dispatch派發action,從而改變store狀態

改進Redux(1)—容器組件和傻瓜組件

分析上面的Redux例子中的Counter組件和Summary組件可以發現:在Redux框架下,一個React組件基本上就是要完成以下兩個功能:

  • 和Redux Store打交道,讀取Store的狀態,用於初始化組件的狀態,同時還要監聽Store的狀態改變;當Store狀態發生變化時,需要更新組件狀態,從而驅動組件重新渲染;當需要更新Store狀態時,就要派發action對象;
  • 根據當前props和state,渲染出用戶界面

如果發現一個組件做的事情太多了,就可以把這個組件拆分成多個組件,讓每個組件依然只專注做一件事:

  • 負責和Redux Store打交道的組件,處於外層,被稱為容器組件(Container Component)
  • 負責渲染界面的組件,處於內層,叫做展示組件(PresentationalComponent)

image.png

現在改裝一下Counter組件:

import React from 'react'
import store from '../Store.js'
import * as Actions from '../Actions'

//1.容器組件
class CounterContainer extends React.Component{
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
        this.onIncremnet = this.onIncremnet.bind(this);
        this.onDecrement = this.onDecrement.bind(this);
        this.state = this.getOwnState()
    }
    getOwnState(){
        return {value:store.getState()[this.props.caption]}
    }
    
    //同步狀態
    componentDidMount(){store.subscribe(this.onChange)}
    componentWillUnmount(){store.unsubscribe(this.onChange)}   
    onChange(){this.setState(this.getOwnState())}
    
    //派發action
    onIncremnet(){
        store.dispatch(Actions.increment(this.props.caption))
    }
    onDecrement(){
        store.dispatch(Actions.decrement(this.props.caption))
    }
    
    render(){
        return <Counter caption={this.props.caption}
        onIncremnet={this.onIncremnet}
        onDecrement={this.onDecrement}
        value={this.state.value}
        />
    }
}
//默認導出容器組件
export default CounterContainer

    
    
//2.展示組件:寫法一
function Counter(props){
    const {caption,onIncremnet,onDecrement,value}=props
    return(
        <div>
            <input type="button" onClick={onIncremnet} value="+"/>
            <input type="button" onClick={onDecrement} value="-"/>
            <span>{caption} count:{value}</span>
        </div>
    )
}
    
//2.展示組件:寫法二
// function Counter({caption,onIncremnet,onDecrement,value}){
//     return(
//         <div>
//             <input type="button" onClick={onIncremnet} value="+"/>
//             <input type="button" onClick={onDecrement} value="-"/>
//             <span>{caption} count:{value}</span>
//         </div>
//     )
// }

示例源碼

改進Redux(2)—組件context

在Counter和Summary組件文件中,都導入了store。然而在組件中直接導入Store是非常不利於組件復用的。一個應用中,最好只有一個地方需要直接導入Store,這個位置應該是在調用最頂層React組件的位置。在該例子中,這個位置就是應用的入口文件src/index. js

不讓組件直接導入Store,那就只能讓組件的上層組件把Store傳遞下來了。然而props存在缺陷,即所有的組件都要幫助傳遞這個props。React提供了一個叫Context的功能,能夠完美地解決這個問題。

image.png

src/Provider.js

import React from 'react'
class Provider extends React.Component{
    getChildContext(){
        return{
            store:this.props.store
        }
    }
    render(){
        return this.props.children
    }
}

Provider提供函數getChildContext,這個函數了代表Context的對象。為了讓Provider能夠被React認可為一個Context的提供者,還需要指定Provider的childContextTypes屬性。Provider定義類的childContextTypes,必須和getChildContext對應,這樣這樣,Provider的子組件才能訪問到context。

import PropTypes from 'prop-types';
Provider.childContextTypes={
    store:PropTypes.object
}

src/index.js

import store from './Store'
import Provider from './Provider'

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
);

src/views/Counter.js

  • 給CounterContainer類的contextTypes賦值,應與Provider.childContextTypes的值一致,否則無法訪問到context

    import PropTypes from 'prop-types';
    CounterContainer.contextTypes = {
        store: PropTypes.object
    }
    
  • 所有對store的訪問,都是通過this.context.store完成。(同理src/views/Summary.js也是如此)

    componentDidMount(){this.context.store.subscribe(this.onChange)}
    
  • 由於定義了構造函數,所以要用上第二個參數context

    constructor(props,context) {
        super(props,context);
        ...
    }
    

    或者

    constructor() {
        super(...arguments);
        ...
    }
    

    示例源碼

改進Redux(3)—React-Redux

上面的兩個改進Redux應用的方法:第一是把一個組件拆分為容器組件和傻瓜組件,第二是使用React的Context來提供一個所有組件都可以直接訪問的Context。實際上,已經有這樣的一個庫來完成這些工作了,這個庫就是react-redux

npm install --save react-redux

react-redux的兩個最主要功能:

  • Provider:提供包含store的context

  • connect:連接容器組件和傻瓜組件;


Provider

在src/index.js中,react-redux可以讓我們不再使用自己實現的Provider,而是從它里面直接導入:

import {Provider} from 'react-redux'

connect

connect讓我們不再需要定義CounterContainer這樣的容器組件,現在只需導出了一個這樣的語句:

import {connect} from 'react-redux';
...
export default connect(mapStateToProps,mapDispatchToProps)(Counter)

connect接收兩個參數mapStateToProps和mapDispatchToProps。第一次是connect函數的執行,第二次是把connect函數返回的函數再次執行(參數為傻瓜組件Counter),最后產生的就是容器組件。connect函數做了以下兩件事:

  • 把Store上的狀態轉化為內層傻瓜組件的prop

    //mapStateToProps函數:
    function mapStateToProps(state,ownProps){
        return {
            value:state[ownProps.caption]
            //ownProps是prop對象。ownProps.caption表示讀取prop對象中的caption屬性值
            //state[ownProps.caption]相當於獲取caption屬性值所對應store值
            //比如<Counter caption="First" />獲取的就是store中First的屬性值
            //然后傻瓜組件就可以通過props.value獲取對應的值
        }
    }
    
  • 把內層傻瓜組件中的用戶動作轉化為派送給Store的動作

    //mapDispatchToProps函數
    function mapDispatchToProps(dispatch,ownProps){
        return{
            onIncrement:()=>{
                dispatch(Actions.increment(ownProps.caption))
            },
            onDecrement:()=>{
                dispatch(Actions.decrement(ownProps.caption))
            }
        }
    }
    

與前面兩個改進方法相比,Counter組件不用寫同步狀態部分


完整代碼:

//Counter.js
import * as Actions from '../Actions'
import {connect} from 'react-redux';


//把Store上的狀態轉化為內層傻瓜組件的prop
function mapStateToProps(state,ownProps){
    return {
        value:state[ownProps.caption]
    }
}

//把內層傻瓜組件中的用戶動作轉化為派送給Store的動作
function mapDispatchToProps(dispatch,ownProps){
    return{
        onIncrement:()=>{
            dispatch(Actions.increment(ownProps.caption))
        },
        onDecrement:()=>{
            dispatch(Actions.decrement(ownProps.caption))
        }
    }
}
//導出容器組件
export default connect(mapStateToProps,mapDispatchToProps)(Counter)

//傻瓜組件
function Counter(props){
    const {caption,onIncrement,onDecrement,value}=props
    return(
        <div>
            <input type="button" onClick={onIncrement} value="+"/>
            <input type="button" onClick={onDecrement} value="-"/>
            <span>{caption} count:{value}</span>
        </div>
    )
}
//Summary.js
import {connect} from 'react-redux'

//把Store上的狀態轉化為內層傻瓜組件的prop
function mapStateToProps(state) {
    let sum = 0;
    for (const key in state) {
      if (state.hasOwnProperty(key)) {
        sum += state[key];
      }
    }
    return {value: sum};
}

function Summary({value}) {
    return (
      <div>Total Count: {value}</div>
    );
}

export default connect(mapStateToProps)(Summary);
//index.js
import store from './Store'
import {Provider} from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
);

示例源碼

總結

  • 定義action類型

  • 創建action構造函數導出對象

  • 用createStore創建store:包含更新狀態reducer、初始狀態、(StoreEnhancer可選)

  • 創建reducer:包含state和ation參數

  • 從react-redux導入Provider組件,提供context,使在組件內的各個地方在不用導入store的情況下都能訪問store

  • 從react-redux導入connect方法,接受兩個函數,再接收一個傻瓜組件,最后生成一個容器組件。其中一個函數負責把Store上的狀態轉化為傻瓜組件的prop;另一個函數負責把傻瓜組件中的用戶動作轉化為派送給Store的動作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM