React学习笔记(六) Redux

react组件中的通信

react推崇的是单向数据流,自上而下进行数据的传递,但是由下而上或者不在一条数据流上的组件之间的通信就会变的复杂。解决通信问题的方法很多,如果只是父子级关系,父级可以将一个回调函数当作属性传递给子级,子级可以直接调用函数从而和父级通信。

组件层级嵌套到比较深,可以使用上下文getChildContext来传递信息,这样在不需要将函数一层层往下传,任何一层的子级都可以通过this.context直接访问。

兄弟关系的组件之间无法直接通信,它们只能利用同一层的上级作为中转站。而如果兄弟组件都是最高层的组件,为了能够让它们进行通信,必须在它们外层再套一层组件,这个外层的组件起着保存数据,传递信息的作用,这其实就是redux所做的事情。

组件之间的信息还可以通过全局事件来传递。不同页面可以通过参数传递数据,下个页面可以用location.param来获取。其实react本身很简单,难的在于如何优雅高效的实现组件之间数据的交流。

redux的用途和用法

首先,redux并不是必须的,它的作用相当于在顶层组件之上又加了一个组件,作用是进行逻辑运算、储存数据和实现组件尤其是顶层组件的通信。如果组件之间的交流不多,逻辑不复杂,只是单纯的进行视图的渲染,这时候用回调,context就行,没必要用redux,用了反而影响开发速度。但是如果组件交流特别频繁,逻辑很复杂,那redux的优势就特别明显了。我第一次做react项目的时候并没有用redux,所有的逻辑都是在组件内部实现,当时为了实现一个逻辑比较复杂的购物车,洋洋洒洒居然写了800多行代码,回头一看我自己都不知道写的是啥,画面太感人。

先简单说一下redux和react是怎么配合的。react-redux提供了connect和Provider两个好基友,它们一个将组件与redux关联起来,一个将store传给组件。组件通过dispatch发出action,store根据action的type属性调用对应的reducer并传入state和这个action,reducer对state进行处理并返回一个新的state放入store,connect监听到store发生变化,调用setState更新组件,此时组件的props也就跟着变化。

redux的流程

值得注意的是connect,Provider,mapStateToProps,mapDispatchToProps是react-redux提供的,redux本身和react没有半毛钱关系,它只是数据处理中心,没有和react产生任何耦合,是react-redux让它们联系在一起。

接下来具体分析一下,redux以及react-redux到底是怎么实现的。

先说说redux
redux主要由三部分组成:store,reducer,action。

store是一个对象,它有四个主要的方法:

1、dispatch:

用于action的分发——在createStore中可以用middleware中间件对dispatch进行改造,比如当action传入dispatch会立即触发reducer,有些时候我们不希望它立即触发,而是等待异步操作完成之后再触发,这时候用redux-thunk对dispatch进行改造,以前只能传入一个对象,改造完成后可以传入一个函数,在这个函数里我们手动dispatch一个action对象,这个过程是可控的,就实现了异步。

2、subscribe:

监听state的变化——这个函数在store调用dispatch时会注册一个listener监听state变化,当我们需要知道state是否变化时可以调用,它返回一个函数,调用这个返回的函数可以注销监听。 let unsubscribe = store.subscribe(() => {console.log('state发生了变化')}

3、getState:

获取store中的state——当我们用action触发reducer改变了state时,需要再拿到新的state里的数据,毕竟数据才是我们想要的。getState主要在两个地方需要用到,一是在dispatch拿到action后store需要用它来获取state里的数据,并把这个数据传给reducer,这个过程是自动执行的,二是在我们利用subscribe监听到state发生变化后调用它来获取新的state数据,如果做到这一步,说明我们已经成功了。

4、replaceReducer:

替换reducer,改变state修改的逻辑。

store可以通过createStore()方法创建,接受三个参数,经过combineReducers合并的reducer和state的初始状态以及改变dispatch的中间件,后两个参数并不是必须的。store的主要作用是将action和reducer联系起来并改变state。

action:

action是一个对象,其中type属性是必须的,同时可以传入一些数据。action可以用actionCreactor进行创造。dispatch就是把action对象发送出去。

reducer:

reducer是一个函数,它接受一个state和一个action,根据action的type返回一个新的state。根据业务逻辑可以分为很多个reducer,然后通过combineReducers将它们合并,state树中有很多对象,每个state对象对应一个reducer,state对象的名字可以在合并时定义。

像这个样子:

const reducer = combineReducers({
     a: doSomethingWithA,
     b: processB,
     c: c
})

combineReducers:

其实它也是一个reducer,它接受整个state和一个action,然后将整个state拆分发送给对应的reducer进行处理,所有的reducer会收到相同的action,不过它们会根据action的type进行判断,有这个type就进行处理然后返回新的state,没有就返回默认值,然后这些分散的state又会整合在一起返回一个新的state树。

接下来分析一下整体的流程,首先调用store.dispatch将action作为参数传入,同时用getState获取当前的状态树state并注册subscribe的listener监听state变化,再调用combineReducers并将获取的state和action传入。combineReducers会将传入的state和action传给所有reducer,并根据action的type返回新的state,触发state树的更新,我们调用subscribe监听到state发生变化后用getState获取新的state数据。

redux的state和react的state两者完全没有关系,除了名字一样。


上面分析了redux的主要功能,那么react-redux到底做了什么?

React-Redux

如果只使用redux,那么流程是这样的:

component --> dispatch(action) --> reducer --> subscribe --> getState --> component

用了react-redux之后流程是这样的:

component --> actionCreator(data) --> reducer --> component

store的三大功能:dispatch,subscribe,getState都不需要手动来写了。react-redux帮我们做了这些,同时它提供了两个好基友Provider和connect。

Provider是一个组件,它接受store作为props,然后通过context往下传,这样react中任何组件都可以通过context获取store.也就意味着我们可以在任意一个组件里利用dispatch(action)来触发readucer改变state,并用subscribe监听state的变化,然后用getState获取变化后的值。但是并不推荐这样做,它会让数据流变的混乱,过度的耦合也会影响组件的复用,维护起来也更麻烦。

connect --connect(mapStateToProps, mapDispatchToProps, mergeProps, options) 是一个函数,它接受四个参数并且再返回一个函数--wrapWithConnect,wrapWithConnect接受一个组件作为参数wrapWithConnect(component),它内部定义一个新组件Connect(容器组件)并将传入的组件(ui组件)作为Connect的子组件然后return出去。

所以它的完整写法是这样的:connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component)

mapStateToProps(state, [ownProps]):

mapStateToProps 接受两个参数,store的state和自定义的props,并返回一个新的对象,这个对象会作为props的一部分传入ui组件。我们可以根据组件所需要的数据自定义返回一个对象。ownProps的变化也会触发mapStateToProps

function mapStateToProps(state) {
   return { todos: state.todos };
}

mapDispatchToProps(dispatch, [ownProps]):

mapDispatchToProps如果是对象,那么会和store绑定作为props的一部分传入ui组件。如果是个函数,它接受两个参数,bindActionCreators会将action和dispatch绑定并返回一个对象,这个对象会和ownProps一起作为props的一部分传入ui组件。所以不论mapDispatchToProps是对象还是函数,它最终都会返回一个对象,如果是函数,这个对象的key值是可以自定义的

function mapDispatchToProps(dispatch) {
   return {
      todoActions: bindActionCreators(todoActionCreators, dispatch),
      counterActions: bindActionCreators(counterActionCreators, dispatch)
   };
}

mapDispatchToProps返回的对象其属性其实就是一个个actionCreator,因为已经和dispatch绑定,所以当调用actionCreator时会立即发送action,而不用手动dispatch。ownProps的变化也会触发mapDispatchToProps。

mergeProps(stateProps, dispatchProps, ownProps):

将mapStateToProps() 与 mapDispatchToProps()返回的对象和组件自身的props合并成新的props并传入组件。默认返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。

options:

pure = true 表示Connect容器组件将在shouldComponentUpdate中对store的state和ownProps进行浅对比,判断是否发生变化,优化性能。为false则不对比。

其实connect函数并没有做什么,大部分的逻辑都是在它返回的wrapWithConnect函数内实现的,确切的说是在wrapWithConnect内定义的Connect组件里实现的。

关于connect的源码

整体看一下,connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,该组件具有如下特点:

  • 通过props.store获取祖先Component的store
  • props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component
  • componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
  • shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
  • componentWillUnmount时移除注册的事件this.handleChange

由于connect的源码过长,我们只看主要逻辑:

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  return function wrapWithConnect(WrappedComponent) {
    class Connect extends Component {
      constructor(props, context) {
        // 从祖先Component处获得store
        this.store = props.store || context.store
        this.stateProps = computeStateProps(this.store, props)
        this.dispatchProps = computeDispatchProps(this.store, props)
        this.state = { storeState: null }
        // 对stateProps、dispatchProps、parentProps进行合并
        this.updateState()
      }
      shouldComponentUpdate(nextProps, nextState) {
        // 进行判断,当数据发生改变时,Component重新渲染
        if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
          this.updateState(nextProps)
            return true
          }
        }
        componentDidMount() {
          // 改变Component的state
          this.store.subscribe(() = {
            this.setState({
              storeState: this.store.getState()
            })
          })
        }
        render() {
          // 生成包裹组件Connect
          return (
            <WrappedComponent {...this.nextState} />
          )
        }
      }
      Connect.contextTypes = {
        store: storeShape
      }
      return Connect;
    }
  }

下面是一个完整的 react --> redux --> react 流程:

一、Provider组件接受redux的store作为props,然后通过context往下传。

二、connect函数在初始化的时候会将mapDispatchToProps对象绑定到store,如果mapDispatchToProps是函数则在Connect组件获得store后,根据传入的store.dispatch和action通过bindActionCreators进行绑定,再将返回的对象绑定到store,connect函数会返回一个wrapWithConnect函数,同时wrapWithConnect会被调用且传入一个ui组件,wrapWithConnect内部使用class Connect extends Component定义了一个Connect组件,传入的ui组件就是Connect的子组件,然后Connect组件会通过context获得store,并通过store.getState获得完整的state对象,将state传入mapStateToProps返回stateProps对象、mapDispatchToProps对象或mapDispatchToProps函数会返回一个dispatchProps对象,stateProps、dispatchProps以及Connect组件的props三者通过Object.assign(),或者mergeProps合并为props传入ui组件。然后在ComponentDidMount中调用store.subscribe,注册了一个回调函数handleChange监听state的变化。

三、此时ui组件就可以在props中找到actionCreator,当我们调用actionCreator时会自动调用dispatch,在dispatch中会调用getState获取整个state,同时注册一个listener监听state的变化,store将获得的state和action传给combineReducers,combineReducers会将state依据state的key值分别传给子reducer,并将action传给全部子reducer,reducer会被依次执行进行action.type的判断,如果有则返回一个新的state,如果没有则返回默认。combineReducers再次将子reducer返回的单个state进行合并成一个新的完整的state。此时state发生了变化。dispatch在state返回新的值之后会调用所有注册的listener函数其中包括handleChange函数,handleChange函数内部首先调用getState获取新的state值并对新旧两个state进行浅对比,如果相同直接return,如果不同则调用mapStateToProps获取stateProps并将新旧两个stateProps进行浅对比,如果相同,直接return结束,不进行后续操作。如果不相同则调用this.setState()触发Connect组件的更新,传入ui组件,触发ui组件的更新,此时ui组件获得新的props,react --> redux --> react 的一次流程结束。

上面的有点复杂,简化版的流程是:

一、Provider组件接受redux的store作为props,然后通过context往下传。

二、connect函数收到Provider传出的store,然后接受三个参数mapStateToProps,mapDispatchToProps和组件,并将state和actionCreator以props传入组件,这时组件就可以调用actionCreator函数来触发reducer函数返回新的state,connect监听到state变化调用setState更新组件并将新的state传入组件。

connect可以写的非常简洁,mapStateToProps,mapDispatchToProps只不过是传入的回调函数,connect函数在必要的时候会调用它们,名字不是固定的,甚至可以不写名字。

简化版本:

@connect(state => state, action)

原文地址:https://www.cnblogs.com/yongrizy/p/15173973.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom