最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入浅出 React -- 组件数据流

    正文概述 掘金(lyn-ho)   2020-12-09   650
    UI = f(data)
    
    UI = render(props + state)
    

    在 React 组件中,props(入参)或者 state (状态)发生改变,UI 也会相应的更新。

    组件更新不止来自自身状态的改变,而两个组件之间建立数据上的连接,实现组件间的通信,它的背后是 React 数据流解决方案。下面将会从各方面说明当前实践中 React 数据通信的方式。

    基于 props 的单向数据流

    React 非常灵活,但它也有一个严格的规则:

    而通过 props 通信的的组件也需遵循单向数据流(单向绑定)的原则

    所谓单向数据流,是指 props 只能流向组件树中比自己层级更低的组件。React 单向数据流的思想使得组件模块化,易于快速开发。基于 props 这种传参方式,可以轻松实现“父子组件通信”、“子父组件通信”、“兄弟组件通信”。

    父子组件通信

    父组件直接将 props 传入子组件

    ?:

    function Child(props) {
      return <div>{`接收来自父组件的内容:${props.text}`}</div>
    }
    
    class Father extends React.Component {
      state = {
        text: '初始化父组件文本'
      }
    
      changeText = () => {
        this.setState({
          text: '改变后的父组件文本'
        })
      }
    
      render() {
        return (
          <div>
            <button onClick={this.changeText}>
              修改文本
            </button>
            <Child text={this.state.text} />
          </div>
        )
      }
    }
    

    子组件读取了来自父组件 props 的内容,并且在父组件 state.text 更改后保持一致

    子父组件通信

    由于单向数据流,子组件不能直接将自身数据塞给父组件;但是父组件可以通过 props 传递给子组件一个绑定自身上下文的函数,那么子组件就可以将想要传递给父组件的数据以函数参数的形式交予父组件,从而间接实现子组件向父组件的数据通信

    ?:

    class Child extends React.Component {
      state = {
        text: '子组件'
      }
    
      changeText = () => {
        this.props.changeText(this.state.text)
      }
    
      render() {
        return (
          <div>
            <button onClick={this.changeText}>点击更新父组件文本</button>
          </div>
        )
      }
    }
    
    class Father extends React.Component {
      state = {
        text: '初始化父组件文本'
      }
    
      changeText = (newText) => {
        this.setState({
          text: newText
        })
      }
    
      render() {
        return (
          <div>
            <p>{`父组件文本:${this.state.text}`}</p>
            <Child changeText={this.changeText} />
          </div>
        )
      }
    }
    

    子组件通过 props.changeText 传入自身的 state.text 调用,修改了父组件的 state.text ,从而实现了子父组件通信

    兄弟组件通信

    兄弟组件共有同一个父组件,可以通过父子组件通信和子父组件通信结合实现兄弟组件通信

    ?:

    function Child1(props) {
      return <div>{`接收来自父组件的内容:${props.text}`}</div>
    }
    
    class Child2 extends React.Component {
      state = {
        text: '来自 Child2 的文本'
      }
    
      changeText = () => {
        this.props.changeText(this.state.text)
      }
    
      render() {
        return (
          <div>
            <button onClick={this.changeText}>点击更新 Child1 组件文本</button>
          </div>
        )
      }
    }
    
    class Father extends React.Component {
      state = {
        text: '初始化父组件文本'
      }
    
      changeText = (newText) => {
        this.setState({
          text: newText
        })
      }
    
      render() {
        return (
          <div>
            { /** 父子组件通信 */}
            <Child1 text={this.state.text} />
            {/** 子父组件通信 */}
            <Child2 changeText={this.changeText} />
          </div>
        )
      }
    }
    

    子组件 Child2 通过 props.changeText 调用更新了父组件的 state.text (子父通信),子组件 Child1 通过 props.text 获取了父组件的 state.text (父子通信),从而实现了兄弟组件之间的通信。

    不推荐其他场景使用 props

    比如多层组件通信,通过 props 层层传递非常简单,但是会编写非常繁琐冗余的代码,并且中间层的组件会引入很多不必要的属性。编写代码的程序员会非常痛苦,而且会是整个项目的维护成本变高。

    针对其他场景的组件通信可以通过其他方式实现。

    使用 Context API

    在 React 16.3 之前,Context API 存在种种局限性,不被 React 官方推荐使用,只作为一个概念探讨。从 React 16.3 之后,对 Context API 进行了改进,使其具备更高的可用性。

    Context API 工作流

    Context API 有 3 个关键 API:React.createContextContext.ProviderContext.Consumer

    React.createContext

    const MyContext = React.createContext(defaultValue)
    

    当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

    只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。

    Context.Provider

    <MyContext.Provider value={}></MyContext.Provider>
    

    每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

    Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

    当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

    Context.Consumer

    <MyContext.Consumer>
      {value => /* 基于 context 值进行渲染*/}
    </MyContext.Consumer>
    

    一个 React 组件可以订阅 context 的变更,这让你在函数式组件中可以订阅 context。

    这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext()defaultValue

    新的 Context API 解决了什么问题

    新的 Context API 改进了这一点:即便组件的 shouldComponentUpdate 返回 false,它仍然可以“穿透”组件继续向后代组件进行传播,进而确保了数据生产者和数据消费者之间数据的一致性。

    "发布-订阅"模式

    "发布-订阅"模式是解决通信类问题的"万能钥匙",有着广泛的应用,比如:

    • ajax 的 successerror 等事件

    • Node.js 中的 EventEmitter

    • Vue.js 的事件总线(EventBus

    理解"发布-订阅"

    发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

    发布-订阅模式的优点是监听事件和触发事件的地方是不受限制的,只要它们在同一个上下文,就可以触发监听。这非常适合任意组件的通信

    设计发布-订阅模式 API

    针对事件的监听(订阅)事件的触发(发布)

    • on :注册事件的监听,指定事件触发时的回调
    • emit :触发事件,可以通过参数在触发时携带数据
    • off :删除监听
    • once :和 on 一样,不过在触发事件后删除监听

    实现发布-订阅

    class EventEmitter {
      constructor() {
        this.eventMap = Object.create(null)
      }
    
      on(type, handler) {
        if(!handler instanceof Function) {
          throw new Error('handler 必须是一个函数')
        }
    
        this.eventMap[type] ? this.eventMap[type].push(handler) : this.eventMap[type] = [handler]
    
        return this
      }
    
      emit(type, params) {
        this.eventMap[type] && this.eventMap[type].forEach((handler) => handler(params))
    
        return this
      }
    
      off(type, handler) {
        if(this.eventMap[type]) {
          if(!handler) {
            delete this.eventMap[type]
          } else {
            this.eventMap[type] = this.eventMap[type].filter(c => c !== handler)
          }
        }
    
        return this
      }
    
      once(type, handler) {
        this.on(type, (...args) => {
          handler(...args)
          this.off(type, handler)
        })
    
        return this
      }
    }
    
    const myEvent = new EventEmitter()
    
    const testHandler = (params) => {
      console.log(`test 事件被触发了,testHandler 接收的参数:${params}`)
    }
    
    myEvent.on('test', testHandler)
    myEvent.emit('test', 'emit') // test 事件被触发了,testHandler 接收的参数:emit
    
    myEvent.off('test', testHandler)
    myEvent.emit('test', 'emit')
    

    组件通信应用

    对于任意两个组件 A 和 B,可以通过 EventEmitter 将数据从 A 传入 B,从而实现组件之间通信

    window.myEvent = new EventEmitter()
    
    class A extends React.Component {
      state = {
        info: '来自A'
      }
    
      toB = () => {
        window.myEvent.emit('eventKey', this.state.info)
      }
    
      render() {
        return <button onClick={this.toB}>点击传递 info 到B</button>
      }
    }
    
    class B extends React.Component {
      state = {
        params: ''
      }
    
      handler = (params) => {
        this.setState({
          params
        })
      }
    
      bindHandler = () => {
        window.myEvent.on('eventKey', this.handler)
      }
    
      render() {
        return (
          <div>
            <button onClick={this.bindHandler}>点我监听 A 的动作</button>
            <div>A 传入的内容是:{this.state.params}</div>
          </div>
        )
      }
    }
    
    function App() {
      return (
        <div>
          <B />
          <A />
        </div>
      )
    }
    

    上面代码的关系图:

    深入浅出 React -- 组件数据流

    第三方数据流框架 -- Redux

    对于简单的跨层级组件通信,可以通过 Context API 或者发布-订阅模式解决。但是随着应用复杂度提升,需要维护的状态增多,我们就需要 Redux 来处理。

    什么是 Redux

    应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers

    Redux 是如何帮助 React 管理数据的

    Redux 主要由三个部分组成:storereduceraction

    • action :把数据从应用传到 store 的有效载荷(对变化的描述)

    • reducer :一个纯函数,负责对变化的分发和处理,并将新的数据返回给 store

    • store :唯一数据源,而且只读

    Redux 工作流:

    深入浅出 React -- 组件数据流

    在 Redux 的整个工作流程中,数据流是严格单向的

    如何理解上图工作流:

    • 视图(View)的所有数据 state 都来自 store

    • 对数据修改的唯一途径:派发 action

    • action 会通过 reducer,根据 action 的内容对数据进行处理,生产新的 state ,最后更新到 store

    对于组件来说,可以通过约定的方式获取 store 的状态,也可以通过派发 action 修改 store 的状态。

    工作流编码

    1. 创建 store 对象

    import { createStore } from 'redux'
    
    const store = createStore(
    	reducer,
      initialState,
      applyMiddleware(middleware1, middleware2, ...)
    )
    

    参数:

    • reducer
    • 初始状态
    • 中间件

    2. 通过 reducer 将新的 state 更新到 store

    const reducer = (state, action) => {
      // 逻辑处理
      return newState
    }
    

    3. action 对象通知 reducer 做什么更新

    const action = {
      type: 'ADD',
      payload: 3
    }
    

    action 对象允许多个属性,只有 type 是必填属性

    typeaction 的唯一标识,reducer 通过不同的 type 来识别更新不同的 state,从而能够实现精准的“定向更新”

    4. 通过 dispatch 派发 action

    import { createStore } from 'redux'
    
    const reducer = (state, action) => {
      // 逻辑处理
      return newState
    }
    
    const store = createStore(reducer)
    
    const action = {
      type: 'ADD',
      payload: 3
    }
    
    store.dispatch(action)
    

    总结

    Redux & React 工作流:

    深入浅出 React -- 组件数据流

    现在我们大致了解 Redux 是如何帮助 React 做状态管理,实现灵活的组件间通信


    起源地下载网 » 深入浅出 React -- 组件数据流

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元