来口大啤酒 發表於 2020-10-6 15:35:00

redux和react-redux的区别及用法

<blockquote>
<p>笔记,参考程墨老师的《深入浅出React和Redux》。阅读之前可以先了解flux框架</p>
</blockquote>
<h1 id="redux框架">Redux框架</h1>
<p><img src="https://i.loli.net/2020/10/04/dmANJeSbQYyE47B.png" alt="image.png" loading="lazy"></p>
<h1 id="redux原则">Redux原则</h1>
<p>Flux的基本原则是“单向数据流”, Redux在此基础上强调三个基本原则:</p>
<ul>
<li>
<p><strong>唯一数据源</strong></p>
<p>唯一数据源指的是应用的状态数据应该只存储在唯一的一个Store上。这个唯一Store上的状态,是一个树形的对象,每个组件往往只是用树形对象上一部分的数据,而如何设计Store上状态的结构,就是Redux应用的核心问题</p>
</li>
<li>
<p><strong>保持状态只读</strong></p>
<p>保持状态只读,就是说不能去直接修改状态,要修改Store的状态,必须要通过派发一个action对象完成。改变状态的方法不是去修改状态上值,而是创建一个新的状态对象返回给Redux,由Redux完成新的状态的组装。</p>
</li>
<li>
<p><strong>数据改变只能通过纯函数完成</strong></p>
<p>这里所说的纯函数就是Reducer, Redux这个名字的前三个字母Red代表的就是Reducer。按照创作者Dan Abramov的说法,Redux名字的含义是Reducer+Flux</p>
<pre><code class="language-js">reducer(state,action)
</code></pre>
<p>第一个参数state是当前的状态,第二个参数action是接收到的action对象,而<strong>reducer函数要做的事情,就是根据state和action的值产生一个新的对象返回</strong>,注意reducer必须是纯函数,也就是说函数的返回结果必须完全由参数state和action决定,而且不产生任何副作用,也不能修改参数state和action对象</p>
<pre><code class="language-js">function reducer (state,action)=&gt;{
    const {counterCaption}=action
    switch(action.type){
      case ActionTypes.INCREMENT:
            return{...state,:state+1}
      case ActionTypes.DECREMENT:
            return{...state,:state-1}
      default:
              return state
    }
}
</code></pre>
</li>
</ul>
<h1 id="redux示例">Redux示例</h1>
<h2 id="示例">示例</h2>
<p>基本目录如下:</p>
<p><img src="https://i.loli.net/2020/10/04/wB6QbuJXvFECmd1.png" alt="image.png" loading="lazy"></p>
<p>效果:</p>
<p>示例源码<br>
<br><br>
<img src="https://uploader.shimo.im/f/OZ37Z4vPginMZIZe.gif" width="200px"><br>
<br></p>
<h2 id="安装">安装</h2>
<pre><code class="language-bash">npm i --save redux
</code></pre>
<h2 id="action">Action</h2>
<p>和Flux一样,<strong>Redux应用习惯上把action类型和action构造函数分成两个文件定义</strong></p>
<p>src/ActionTypes.js:</p>
<pre><code class="language-js">export const INCREMENT='increment'
export const DECREMENT='decrement'
</code></pre>
<p><strong>src/Actions.js</strong>:</p>
<pre><code class="language-js">import * as ActionTypes from './ActionTypes.js';

export const increament=(counterCaption)=&gt;{
    return {
      type:ActionTypes.INCREMENT,
      counterCaption:counterCaption
    }
}
export const decrement=(counterCaption)=&gt;{
    return {
      type:ActionTypes.DECREMENT,
      counterCaption:counterCaption
    }
}
</code></pre>
<blockquote>
<p><strong>对比发现</strong>:<strong>Redux中每个action构造函数都返回一个action对象</strong>。而Flux版本中action构造函数则直接调用Dispatcher的dispatch函数把action对象派发出去。即redux与flux的区别在于,在actions文件中,redux不负责派发</p>
<pre><code class="language-js">//flux版本
import * as AcitonTypes from './ActionTypes';
import AppDispatcher from './AppDispatcher'

export const increment = (counterCaption)=&gt;{
AppDispatcher.dispatch({
   type:AcitonTypes.INCREMENT,
   counterCaption:counterCaption
})
}
export const decrement = (counterCaption)=&gt;{
AppDispatcher.dispatch({
   type:AcitonTypes.DECREMENT,
   counterCaption:counterCaption
})
}
</code></pre>
</blockquote>
<h2 id="store">Store</h2>
<p><strong>Redux库提供的createStore函数</strong>:第一个参数代表更新状态的reducer,第二个参数是状态的初始值,第三个参数可选,代表StoreEnhancer。状态通过getStore()获取</p>
<p><strong>src/Store.js</strong></p>
<pre><code class="language-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
</code></pre>
<h2 id="reducer">Reducer</h2>
<p>用于更新状态。任何通过dispatch派发的action都会通过这里来改变store状态</p>
<p><strong>src/Reducer.js</strong></p>
<pre><code class="language-js">import * as ActionTypes from './ActionTypes.js'
export default (state,action)=&gt;{
    const {counterCaption}=action
    switch(action.type){
      case ActionTypes.INCREMENT:
            return{...state,:state+1}
      case ActionTypes.DECREMENT:
            return{...state,:state-1}
      default:
              return state
    }
}
</code></pre>
<blockquote>
<p><strong>对比发现</strong>:Redux比Flux多了一个参数state,而在Flux中没有state参数。即redux与flux的区别在于,Flux中的state是由Store管理的,而Redux中的state由Redux本身管理。</p>
<pre><code class="language-js">//flux版本
CounterStore.dispatchToken=AppDispatcher.register((action)=&gt;{
if(action.type===AcitonTypes.INCREMENT){
   counterValues++
   CounterStore.emitChange()
}else if(action.type===AcitonTypes.DECREMENT){
   counterValues--
   CounterStore.emitChange()
}
})
</code></pre>
</blockquote>
<h2 id="view">View</h2>
<p>src/views/ControlPanel.js</p>
<pre><code class="language-js">import React from 'react'
import Counter from './Counter'
import Summary from './Summary'

export default class ControlPanel extends React.Component{
    render(){
      return(
              &lt;div&gt;
                    &lt;Counter caption="First" /&gt;
                &lt;Counter caption="Second" /&gt;
                &lt;Counter caption="Third" /&gt;
                &lt;hr/&gt;
                &lt;Summary/&gt;
            &lt;/div&gt;
      )
    }
}
</code></pre>
<p><strong>src/views/Counter.js</strong></p>
<pre><code class="language-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()}
    }
   
    //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(
              &lt;div&gt;
                    &lt;input type="button" onClick={this.onClickIncrementButton} value="+"/&gt;
                &lt;input type="button" onClick={this.onClickDecrementButton} value="-"/&gt;
                &lt;span&gt;{caption} count:{value}&lt;/span&gt;
            &lt;/div&gt;
      )
    }
}
</code></pre>
<p><strong>src/views/Summary.js</strong></p>
<pre><code class="language-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
            }
      }
      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(
              &lt;div&gt;
                    total:{this.state.sum}
            &lt;/div&gt;
      )
    }
}
</code></pre>
<h2 id="总结">总结</h2>
<ul>
<li>定义action类型</li>
<li>创建action构造函数</li>
<li>用createStore创建store:包含更新状态reducer、初始状态、(StoreEnhancer可选)</li>
<li>创建reducer:包含state和ation参数</li>
<li>view部分初始化状态:通过store.getStore()获取状态</li>
<li>通过subscribe和unsubscribe同步状态</li>
<li>通过store.dispatch派发action,从而改变store状态</li>
</ul>
<h1 id="改进redux1容器组件和傻瓜组件">改进Redux(1)—容器组件和傻瓜组件</h1>
<p>分析上面的Redux例子中的Counter组件和Summary组件可以发现:在Redux框架下,一个React组件基本上就是要完成以下两个功能:</p>
<ul>
<li>和Redux Store打交道,读取Store的状态,用于初始化组件的状态,同时还要监听Store的状态改变;当Store状态发生变化时,需要更新组件状态,从而驱动组件重新渲染;当需要更新Store状态时,就要派发action对象;</li>
<li>根据当前props和state,渲染出用户界面</li>
</ul>
<p>如果发现一个组件做的事情太多了,就可以把这个组件拆分成多个组件,让每个组件依然只专注做一件事:</p>
<ul>
<li>负责和Redux Store打交道的组件,处于外层,被称为<strong>容器组件(Container Component)</strong></li>
<li>负责渲染界面的组件,处于内层,叫做<strong>展示组件(PresentationalComponent)</strong></li>
</ul>
<p><img src="https://i.loli.net/2020/10/04/bVSwjTvZUXIatzR.png" alt="image.png" loading="lazy"></p>
<p>现在改装一下Counter组件:</p>
<pre><code class="language-js">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()}
    }
   
    //同步状态
    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 &lt;Counter caption={this.props.caption}
      onIncremnet={this.onIncremnet}
      onDecrement={this.onDecrement}
      value={this.state.value}
      /&gt;
    }
}
//默认导出容器组件
export default CounterContainer

   
   
//2.展示组件:写法一
function Counter(props){
    const {caption,onIncremnet,onDecrement,value}=props
    return(
      &lt;div&gt;
            &lt;input type="button" onClick={onIncremnet} value="+"/&gt;
            &lt;input type="button" onClick={onDecrement} value="-"/&gt;
            &lt;span&gt;{caption} count:{value}&lt;/span&gt;
      &lt;/div&gt;
    )
}
   
//2.展示组件:写法二
// function Counter({caption,onIncremnet,onDecrement,value}){
//   return(
//         &lt;div&gt;
//             &lt;input type="button" onClick={onIncremnet} value="+"/&gt;
//             &lt;input type="button" onClick={onDecrement} value="-"/&gt;
//             &lt;span&gt;{caption} count:{value}&lt;/span&gt;
//         &lt;/div&gt;
//   )
// }
</code></pre>
<p>示例源码</p>
<h1 id="改进redux2组件context">改进Redux(2)—组件context</h1>
<p>在Counter和Summary组件文件中,都导入了store。然而在组件中直接导入Store是非常不利于组件复用的。一个应用中,最好只有一个地方需要直接导入Store,这个位置应该是在调用最顶层React组件的位置。在该例子中,这个位置就是应用的入口文件src/index. js</p>
<p>不让组件直接导入Store,那就只能让组件的上层组件把Store传递下来了。然而props存在缺陷,即所有的组件都要帮助传递这个props。React提供了一个叫Context的功能,能够完美地解决这个问题。</p>
<p><img src="https://i.loli.net/2020/10/05/THxjK6mbSwzMVlI.png" alt="image.png" loading="lazy"></p>
<p><strong>src/Provider.js</strong></p>
<pre><code class="language-js">import React from 'react'
class Provider extends React.Component{
    getChildContext(){
      return{
            store:this.props.store
      }
    }
    render(){
      return this.props.children
    }
}
</code></pre>
<p><strong>Provider提供函数getChildContext,这个函数了代表Context的对象</strong>。为了让Provider能够被React认可为一个Context的提供者,还需要指定Provider的childContextTypes属性。<strong>Provider定义类的childContextTypes,必须和getChildContext对应</strong>,这样这样,Provider的子组件才能访问到context。</p>
<pre><code class="language-js">import PropTypes from 'prop-types';
Provider.childContextTypes={
    store:PropTypes.object
}
</code></pre>
<p><strong>src/index.js</strong></p>
<pre><code class="language-jsx">import store from './Store'
import Provider from './Provider'

ReactDOM.render(
&lt;Provider store={store}&gt;
    &lt;App/&gt;
&lt;/Provider&gt;,
document.getElementById('root')
);
</code></pre>
<p><strong>src/views/Counter.js</strong></p>
<ul>
<li>
<p>给CounterContainer类的contextTypes赋值,应与Provider.childContextTypes的值一致,否则无法访问到context</p>
<pre><code class="language-js">import PropTypes from 'prop-types';
CounterContainer.contextTypes = {
    store: PropTypes.object
}
</code></pre>
</li>
<li>
<p>所有对store的访问,都是通过this.context.store完成。(同理src/views/Summary.js也是如此)</p>
<pre><code class="language-js">componentDidMount(){this.context.store.subscribe(this.onChange)}
</code></pre>
</li>
<li>
<p>由于定义了构造函数,所以要用上第二个参数context</p>
<pre><code class="language-js">constructor(props,context) {
    super(props,context);
    ...
}
</code></pre>
<p>或者</p>
<pre><code class="language-js">constructor() {
    super(...arguments);
    ...
}
</code></pre>
<p>示例源码</p>
</li>
</ul>
<h1 id="改进redux3react-redux">改进Redux(3)—React-Redux</h1>
<p>上面的两个改进Redux应用的方法:第一是把一个组件拆分为容器组件和傻瓜组件,第二是使用React的Context来提供一个所有组件都可以直接访问的Context。实际上,已经有这样的一个库来完成这些工作了,这个库就是react-redux</p>
<pre><code class="language-bash">npm install --save react-redux
</code></pre>
<p><strong>react-redux的两个最主要功能:</strong></p>
<ul>
<li>
<p>Provider:提供包含store的context</p>
</li>
<li>
<p>connect:连接容器组件和傻瓜组件;</p>
</li>
</ul>
<hr>
<h2 id="provider">Provider</h2>
<p>在src/index.js中,react-redux可以让我们不再使用自己实现的Provider,而是从它里面直接导入:</p>
<pre><code class="language-js">import {Provider} from 'react-redux'
</code></pre>
<h2 id="connect">connect</h2>
<p>connect让我们不再需要定义CounterContainer这样的容器组件,现在只需导出了一个这样的语句:</p>
<pre><code class="language-js">import {connect} from 'react-redux';
...
export default connect(mapStateToProps,mapDispatchToProps)(Counter)
</code></pre>
<p><strong>connect接收两个参数mapStateToProps和mapDispatchToProps</strong>。第一次是connect函数的执行,第二次是把connect函数返回的函数再次执行(参数为傻瓜组件Counter),最后产生的就是容器组件。<strong>connect函数做了以下两件事:</strong></p>
<ul>
<li>
<p><strong>把Store上的状态转化为内层傻瓜组件的prop</strong></p>
<pre><code class="language-js">//mapStateToProps函数:
function mapStateToProps(state,ownProps){
    return {
      value:state
      //ownProps是prop对象。ownProps.caption表示读取prop对象中的caption属性值
      //state相当于获取caption属性值所对应store值
      //比如&lt;Counter caption="First" /&gt;获取的就是store中First的属性值
      //然后傻瓜组件就可以通过props.value获取对应的值
    }
}
</code></pre>
</li>
<li>
<p><strong>把内层傻瓜组件中的用户动作转化为派送给Store的动作</strong></p>
<pre><code class="language-js">//mapDispatchToProps函数
function mapDispatchToProps(dispatch,ownProps){
    return{
      onIncrement:()=&gt;{
            dispatch(Actions.increment(ownProps.caption))
      },
      onDecrement:()=&gt;{
            dispatch(Actions.decrement(ownProps.caption))
      }
    }
}
</code></pre>
</li>
</ul>
<blockquote>
<p>与前面两个改进方法相比,Counter组件不用写同步状态部分</p>
</blockquote>
<hr>
<p>完整代码:</p>
<pre><code class="language-jsx">//Counter.js
import * as Actions from '../Actions'
import {connect} from 'react-redux';


//把Store上的状态转化为内层傻瓜组件的prop
function mapStateToProps(state,ownProps){
    return {
      value:state
    }
}

//把内层傻瓜组件中的用户动作转化为派送给Store的动作
function mapDispatchToProps(dispatch,ownProps){
    return{
      onIncrement:()=&gt;{
            dispatch(Actions.increment(ownProps.caption))
      },
      onDecrement:()=&gt;{
            dispatch(Actions.decrement(ownProps.caption))
      }
    }
}
//导出容器组件
export default connect(mapStateToProps,mapDispatchToProps)(Counter)

//傻瓜组件
function Counter(props){
    const {caption,onIncrement,onDecrement,value}=props
    return(
      &lt;div&gt;
            &lt;input type="button" onClick={onIncrement} value="+"/&gt;
            &lt;input type="button" onClick={onDecrement} value="-"/&gt;
            &lt;span&gt;{caption} count:{value}&lt;/span&gt;
      &lt;/div&gt;
    )
}
</code></pre>
<pre><code class="language-jsx">//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;
      }
    }
    return {value: sum};
}

function Summary({value}) {
    return (
      &lt;div&gt;Total Count: {value}&lt;/div&gt;
    );
}

export default connect(mapStateToProps)(Summary);
</code></pre>
<pre><code class="language-jsx">//index.js
import store from './Store'
import {Provider} from 'react-redux'

ReactDOM.render(
&lt;Provider store={store}&gt;
    &lt;App/&gt;
&lt;/Provider&gt;,
document.getElementById('root')
);
</code></pre>
<p>示例源码</p>
<h1 id="总结-1">总结</h1>
<ul>
<li>
<p>定义action类型</p>
</li>
<li>
<p>创建action构造函数导出对象</p>
</li>
<li>
<p>用createStore创建store:包含更新状态reducer、初始状态、(StoreEnhancer可选)</p>
</li>
<li>
<p>创建reducer:包含state和ation参数</p>
</li>
<li>
<p>从react-redux导入Provider组件,提供context,使在组件内的各个地方在不用导入store的情况下都能访问store</p>
</li>
<li>
<p>从react-redux导入connect方法,接受两个函数,再接收一个傻瓜组件,最后生成一个容器组件。其中一个函数负责把Store上的状态转化为傻瓜组件的prop;另一个函数负责把傻瓜组件中的用户动作转化为派送给Store的动作</p>
</li>
</ul><br><br>
来源:https://www.cnblogs.com/sanhuamao/p/13773556.html
頁: [1]
查看完整版本: redux和react-redux的区别及用法