Reac组件化以及组件通信

Posted 面条请不要欺负汉堡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Reac组件化以及组件通信相关的知识,希望对你有一定的参考价值。

一.模块与组件以及模块化与组件化慨念

  1. 模块:向外提供特定功能的JS文件,便于复用JS,简化JS,提升JS效率数据、对数据的操作(函数)、将想暴露的私有的函数向外暴露(暴露的数据类型是对象)
    2. 模块化:形容项目编码方式,即按模块编写与组织的项目。
    3. 组件:用来实现特定布局功能效果的代码与资源集合,包含html、css、js、图片资源等,例如一个页面中头部区域的资源集合
    4. 组件化:形容项目的编码方式,即按组件方式编写实现的项目。

二.组件化介绍

(1)React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:

  • 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);
  • 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component
    )和有状态组件(Stateful Component)
  • 根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container
    Component);

(2)这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:

  • 函数组件、无状态组件、展示型组件主要关注UI的展示;
  • 类组件、有状态组件、容器型组件主要关注数据逻辑;

(3)当然还有很多组件的其他概念:比如异步组件、高阶组件等。

三.各类组件介绍

(一) 类组件

类组件的定义要求

	
 - 组件的名称是**大写字符开头**(无论类组件还是函数组件) 
 - 类组件需要继承自 React.Component 
 - 类组件必须实现render函数

在ES6之前,可以通过create-react-class 模块来定义类组件,但是目前官网建议我们使用ES6的class类定义。

使用class定义一个组件

  • constructor是可选的,我们通常在constructor中初始化一些数据;
  • this.state中维护的就是我们组件内部的数据
  • render() 方法是 class 组件中唯一必须实现的方法
//  AntdTest 组件名
export default class AntdTest extends Component 
  render() 
    return (
      <div>
        <Button type="primary">按钮</Button>
      </div>
    )
  

注意:render函数的返回值
当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一

1.react 元素:div还是<MyComponent>均为 React 元素
2.数组或 fragments:使得 render 方法可以返回多个元素。
3.Portals:可以渲染子节点到不同的 DOM 子树中。
4.字符串或数值类型:它们在 DOM 中会被渲染为文本节点
5.布尔类型或 null:什么都不渲染

(二) 函数组件

函数组件是使用function来进行定义的函数,这个函数会返回和类组件中render函数返回一样的内容。

特点

  • 没有生命周期,也会被更新并挂载;
  • 没有this(组件实例);
  • 没有内部状态(state);
// 函数类型的组件
export function Welcome1(props) 
  return <div>Welcome1, props.name</div>

React 函数组件与class组件的区别

(三) 无状态组件(展示组件)

React是通过更新状态,来实现页面的局部动态变化。那么对于一些只做展示的组件,或者一些原子组件,比如说列表中的某一行,一个输入框等,就可以使用无状态组件。由于无状态组件内部不存在状态,因此也可以使用函数式组件进行书写。甚至如果功能比较简单,可以直接使用箭头函数书写。一些UI组件库使用的大多数都是无状态组件

简洁概括

  • 主要用来定义模板,接收来自父组件props传递的数据。
  • 使用props.xxx的表达式把props放入模板中
  • 无状态模板应该保持模板的纯粹性,以便于组件复用。
//无状态组件
const Child = (props)=>(
  <input type="text" value=props.value onChange=props.fun/>
)
或
const PureComponent = (props) => (
    <div>
        //use props
    </div>
)

特征

  • 无状态组件无法访问生命周期的方法,因为它不需要组件生命周期和状态管理。
  • 无状态组件不会被实例化【无实例化过程不需要分配多余的内存,所以性能更优;同样,由于无实例化也导致了无法访问this】

注意:函数组件,展示组件属于无状态组件

(四) 有状态组件

在无状态组件的基础上,如果组件内部包含状态(state)且状态随着事件或者外部的消息而发生改变的时候,这就构成了有状态组件。有状态组件通常会带有生命周期(lifecycle),用以在不同的时刻触发状态的更新。这种组件也是通常在写业务逻辑中最经常使用到的,根据不同的业务场景组件的状态数量以及生命周期机制也不尽相同。

概括

  • 主要用来定义交互逻辑和业务数据。
  • 使用this.state.xxx的表达式把业务数据挂载到容器组件的实例上,然后传递props到展示组件上,展示组件接收到props,把props放入到模板中
class StatefulComponent extends Component 

    constructor(props) 
        super(props);
        this.state = 
            //定义状态
        
    

    componentWillMount() 
        //do something
    
  
    componentDidMount() 
        //do something
    
    ... //其他生命周期

    render() 
        return (
            //render
        );
    

注意:类组件,容器组件 属于有状态组件

(五) 容器组件

在具体的项目实践中,我们通常的前端数据都是通过Ajax请求获取的,而且获取的后端数据也需要进一步的做处理。为了使组件的职责更加单一,引入了容器组件(Container Component)的概念。我们将数据获取以及处理的逻辑放在容器组件中,使得组件的耦合性进一步地降低。

/ 容器组件
export default class CommentList extends Component 
  constructor(props) 
    super(props)
    this.state = 
      comments: [],
    
  
  componentDidMount() 
    var _this = this
    axios.get('/path/to/commentsApi').then(function (response) 
      _this.setState( comments: response.data )
    )
  
  render() 
    return (
      <div>
        this.state.comments.map((c, i) => (
          <Comment key=i ...c />
        ))
      </div>
    )
  

上面这个容器组件,就是负责获取用户数据,然后以props的形式传递给UserList组件来渲染。容器组件也不会在页面中渲染出具体的DOM节点,因此,它通常就充当数据源的角色。目前很多常用的框架,也都采用这种组件形式。如:React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create()等。

(六) 展示组件

只做展示一些数据的信息的组件

// 展示组件
function Comment( data ) 
  return (
    <div>
      ' '
      <p>data.body</p> <p> --- data.author</p>' '
    </div>
  )

展示组件 vs 容器组件

容器组件负责数据获取,展示组件负责根据props显示信息

四.组件嵌套

组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件,再将这些组件组合嵌套在一起,最终形成我们的应用程序;
我们来分析一下下面代码的嵌套逻辑:

import React,  Component  from 'react';

function Header() 
  return <h2>Header</h2>


function Main() 
  return (
    <div>
      <Banner/>
      <ProductList/>
    </div>
  )


function Banner() 
  return <div>Banner</div>


function ProductList() 
  return (
    <ul>
      <li>商品1</li>
      <li>商品2</li>
      <li>商品3</li>
      <li>商品4</li>
      <li>商品5</li>
    </ul>
  )


function Footer() 
  return <h2>Footer</h2>


export default class App extends Component 
  render() 
    return (
      <div>
        <Header/>
        <Main/>
        <Footer/>
      </div>
    )
  

如代码所示:
App组件是Header、Main、Footer组件的父组件;
Main组件是Banner、ProductList组件的父组件;

六. 组件通信

1) ⽗组件向⼦组件通信

传 props 的⽅式:是通过在父组件添加属性,在子组件通过this.props.xxx(类组件使用this.props.xxx;函数组件props.xxx)的形式传值。
思路:父组件向子组件传值,通过props,将父组件的state传递给了子组件。

import React,  Component  from 'react'
export default class Correspond extends Component 
  render() 
    return (
      <div>
        <Chindren name="小白"></Chindren>
      </div>
    )
  


2) ⼦组件向⽗组件通信

可以采用 props + 回调 的⽅式( 通过调用父组件传过来的回调函数)
思路:子组件通过调用父组件传递到子组件的方法向父组件传递消息的。父组件收到参数后将值赋给父组件的state。

import React,  Component  from 'react'

function Chindren(props) 
  return <div>Welcome, props.name</div>

class Welcome extends Component 
  constructor(props) 
    super(props)
    this.state = 
      msg: this.props.toChildren,
    
  
  toParent = () => 
    // 通过props属性获取父组件的getdata方法,并将this.state值传递过去
    this.props.callback('早上好!') //子组件通过此触发父组件的回调方法
  
  render() 
    return (
      <div>
        <button onClick=this.toParent> 副官向上级打招呼</button>
      </div>
    )
  

export default class Correspond extends Component 
  constructor(props) 
    super(props)
    this.state = 
      name: '副官',
      back: '',
    
  
  //用于接收子组件的传值方法,参数为子组件传递过来的值
  changeMsg = (val) => 
    //把子组件传递过来的值赋给this.state中的属性
    this.setState(
      back: val,
    )
  
  render() 
    return (
      <div>
        /* 子组件向父组件传参数 */
        <div>上级:Welcome, this.state.name!</div>
        this.state.back && <div>副高:this.state.back</div>
        /* 注意:子组件中props的回调函数名称 和 父组件传递添加的方法(如:callback) 需一致 */
        <Welcome callback=this.changeMsg></Welcome>
      </div>
    )
  


3) 兄弟组件通信

redux实现和利用父组件(prop 一层一层传递)
思路:兄弟组件之间的传值,是通过父组件做的中转 ,流程为组件A – 传值 --> 父组件 – 传值 --> 组件B

import React,  Component  from 'react'

// Acls组件
class Acls extends Component 
  //按钮点击事件,向父组件传值
  handleClick() 
    this.props.data('hello...React...')
  
  render() 
    return (
      <div>
        <button onClick=this.handleClick.bind(this)>
          Acls组件中获取数据
        </button>
      </div>
    )
  

//Bcls组件
class Bcls extends React.Component 
  render() 
    return <div>在Bcls组件中展示数据:this.props.mess</div>
  


export default class Correspond extends Component 
  constructor(props) 
    super(props)
    this.state = 
      mess: '',
    
  

  //向子组件A 组件 提供的传值方法,参数为获取的子组件传过来的值
  getDatas(data) 
    this.setState(
      mess: data,
    )
  
  render() 
    return (
      <div>
        /* 兄弟组件传值 */
        <Acls data=this.getDatas.bind(this)></Acls>
        <Bcls mess=this.state.mess></Bcls>
      </div>
    )
  


4) 跨组件(如祖父与孙之间通信)

常用的方式有两种,逐层传值与跨层传值。
A. 逐层传值
先父子通信,然后再子“孙”通信,传递的层级变成祖父–>子–>“孙”,同理,通过props往下传,通过回调往上传。
B. 跨层传值

组件跨层级通信可使用Context 。

React官方文档对Context做出了解释:
在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props

一句话概括就是:跨级传值,状态共享

这种模式下有两个角色,Provider和Consumer
Provider为外层组件,用来提供 数据;内部需要数据时用Consumer来读取

祖父——》孙

  1. 新建context.js文件(与父组件同级),默认值为一个对象
// 跨级传值  上下文
import React from 'react'
const MyContext = React.createContext( text: 'luck' )
export default MyContext

  1. 祖父组件编写:在祖父组件引入context,使用一个 Provider 来将当前的 value 传递给以下的组件树,value为传递的值。
import React,  Component  from 'react'
import MyContext from './context'
import Grandson from './Grandson'

// 子组件
class Children extends React.Component 
  render() 
    return (
      <div>
        <Grandson></Grandson>
      </div>
    )
  

export default class Correspond2 extends Component 
  // 使用一个 Provider 来将当前的 value 传递给以下的组件树。
  // 无论多深,任何组件都能读取这个值。
  render() 
    return (
      <div
        style=
          backgroundColor: '#f7ba2a',
          padding: '20px',
          width: '500px',
          margin: 'auto',
          textAlign: 'center',
        
      >
        <p>父组件</p>
        /* 对父组件:引入context,使用一个 Provider 来将当前的 value 传递给以下的组件树,value为传递的值。 */
        <MyContext.Provider value= text: '你好' >
          <Children></Children>
        </MyContext.Provider>
      </div>
    )
  


  1. 孙组件Grandson.js :同样需引入context,在组件内部添加static contextType = MyContext,此时将能通过this.context直接获取到上层距离最近的Provider传递的值,此时this.context = text:good luck,即祖父组件传递value。
import React from 'react'
import MyContext from './context'

class Grandson extends React.Component 
  //  孙组件: 同样需引入context,在组件内部添加static contextType = MyContext,
  //   此时将能通过this.context直接获取到上层距离最近的Provider传递的值,此时this.context = text:good luck,即父组件传递value。
  static contextType = MyContext
  render() 
    return (
      <div
        style=
          backgroundColor: '#13ce66',
          padding: '10px',
          width: '200px',
          margin: 'auto',
          marginTop: '20px',
        
      >
        <p>孙组件:</p>
        <span style= color: 'blue' >this.context.text</span>
      </div>
    )
  


export default Grandson

通过this.context.text获取到传递的值

孙——》祖父传值 ,可以通过回调的方式

  1. 对祖父组件进行传值修改,在传过来的对象中添加一个属性,里面绑定祖父组件的方法value=text:‘good luck’,toParent:this.fromGranson
import React,  Component  from 'react'
import MyContext from './context'
import Grandson from './Grandson'

// 子组件
class Children extends React.Component 
  render() 
    return (
      <div>
        <Grandson></Grandson>
      </div>
    )
  

export default class Correspond2 extends Component 
  // 使用一个 Provider 来将当前的 value 传递给以下的组件树。
  // 无论多深,任何组件都能读取这个值。
  constructor(props) 
    super(props)
    this.state = 
      msg: '',
    
  
  fromGranson = (val) => 
    this.setState(
      msg: val,
    )
  
  render() 
    return (
      <div
        style=
          backgroundColor: '#f7ba2a',
          padding: '20px',
          width: '500px',
          margin: 'auto',
          textAlign: 'center',
        
      >
        <p>祖父组件</p>
        <span style= color: 'red' >this.state.msg</span>
        /* 对父组件:引入context,使用一个 Provider 来将当前的 value 传递给以下的组件树,value为传递的值。 */
        <MyContext.Provider
          value= text: '你好', toParent: this.fromGranson 
        >
          <Children></Children>
        </MyContext.Provider>
      </div>
    )
  


  1. 孙组件:添加一个按钮,绑定方法,执行函数回调
import React from 'react'
import MyContext from './context'

class Grandson extends React.Component 
  //  孙组件: 同样需引入context,在组件内部添加static contextType = MyContext,
  //   此时将能通过this.context直接获取到上层距离最近的Provider传递的值,此时this.context = text:good luck,即父组件传递value。
  static contextType = MyContext
  toParent = () => 
    this.context.toParent('孙组件向祖父组件传数据')
  
  render() 
    return (
      <div
        style=
          backgroundColor: '#13ce66',
          padding: '10px',
          width: '200px',
          margin: 'auto',
          marginTop: '20px',
        
      >
        <p>孙组件:</p>
        <span style= color: 'blue' >this.context.text</span>
        <div>
          <button onClick=this.toParent>向上祖父组件</button>
        </div>
      </div>
    )
  


export default Grandson


注意: 不管层级有多深,都可以使用context进行向下或向上传值。在下层组件中取的context中的字段需与value中传递字段保持一致。text与toParent
官网

Redux

数据共享状态
Redux基本语法

发布订阅模式event

发布订阅模式所有组件都可以。 event的方式比较灵活,不管是父子、跨级、还是同级,甚至毫无关联的组件,都可以使用此方式进行通信

发布订阅模式

组件间通信需要引用一个类的实例,使用单例模式实现。
在 发布/订阅模式 有 发布者 和 订阅者,它们通过信道链接到一起。其主要包含三个对象:
订阅者:订阅一个或者多个信道消息的对象。
发布者:消息的发布者,往信道中投递消息的对象。
信道:每个信道都有一个名字,信道的实现细节对用户代码来说是隐藏的。

订阅 / 发布模型API设计思路

on(): 负责注册事件的监听(订阅),指定事件触发(发布)时的回调函数;
emit(): 负责触发事件,可以通过传参使其在触发的时候携带数据
off():负责监听器的删除(解除事件)

优缺点

在这种模式中,一个目标对象(被观察者)管理所有的依赖于它的对象(观察者),并且在它本身的状态发生变化的时候主动发出通知。
其主要包含两个对象:被观察者和观察者
优点:
跨组件进行通信
利于合作,对于某些操作,其他人不需要清楚具体的逻辑,只需要去发起就行。
缺点:
耦合问题:每个观察者必须和被观察对象绑定在一起,这引入了耦合
性能问题:在最基本的实现中观察对象必须同步地通知观察者。这可能会导致性能瓶颈。

案例

安装event
npm install event -save

  1. 新建event.js
    import EventEmitter from ‘events’;
    export default new EventEmitter();
  2. 另两个组件处于同层级
import React from 'react';
import GrandsonEven from './GrandsonEven';
import GrandsonOtherEven from './GrandsonOtherEven';
 
class Children extends React.Component 
  render()
    return (
      <div>
        <GrandsonEven></GrandsonEven>
        <GrandsonOtherEven></GrandsonOtherEven>
      </div>
    )
  

 
export default Children
  1. 在GrandsonEven组件里。导入event,在componentDidMount阶段添加监听on,事件名称与GrandsonOtherEven组件中emit一致
import React from 'react'
import event from './event'

class GrandsonEven extends React.Component 
  constructor(props) 
    super(props)
    this.state = 
      msg: '',
    
  
  getData = (params) => 
    this.setState(
      msg: params,
    )
  
  componentDidMount() 
    //监听eventMsg
    event.on('eventMsg', this.getData)
  

  render() 
    return (
      <div
        style=
          backgroundColor: '#13ce66',
          padding: '10px',
          width: '200px',
          margin: 'auto',
          marginTop: '20px',
        
      >
        <p>组件一</p>

        <div>组件二通过event传递值过来:this.state.msg</div>
      </div>
    )
  


export default G

以上是关于Reac组件化以及组件通信的主要内容,如果未能解决你的问题,请参考以下文章

vue的通信方式---祖父孙三个级别的之间的隔代通信

来自祖父母的组件,来自父母的数据,在孩子中呈现

如何在 VueJS 中将模板(插槽)从祖父组件传递给孙子?

尝试从组件向其祖父母触发事件时未触发事件

Reactjs 更新祖父母状态

vue provide和inject使用