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 GrandsonEven

注意:在componentWillUnmount移除监听removeListener 这个事件,已经失效了
4. 在GrandsonOtherEven组件,导入event,按钮绑定方法,使用event.emit触发(发布)事件。

import React from 'react'
import event from './event'

class GrandsonOtherEven extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      msg: '',
    }
  }
  toOther = () => {
    //触发test事件的同时,传入希望testHandler感知的参数
    event.emit('eventMsg', '你好')
  }
  render() {
    return (
      <div
        style={{
          backgroundColor: '#13ce66',
          padding: '10px',
          width: '200px',
          margin: 'auto',
          marginTop: '20px',
        }}
      >
        <p>组件二</p>
        <div>
          <button onClick={this.toOther}>event传值</button>
        </div>
      </div>
    )
  }
}

export default GrandsonOtherEven

注意:如果两个组件使用event进行通信,确保发布订阅的事件名称一致,如上例中 eventMsg

在 umi/dva 的开发中,`model` 文件用于定义状态(state)、订阅(subscribers)、动作(reducers)以及副作用(effects)。为了同时支持 React 函数组件使用 `useModel()` 和 React组件使用 `connect`,我们需要确保 `model` 中的状态和订阅是可访问的,并且动作(reducers)和副作用(effects)可以被触发。 下面是一个基本的 `model` 文件的写法示例: ```javascript // model.js import { types } from 'mobx-state-tree'; export default { namespace: 'count', state: { number: 0, }, reducers: { add(state) { return { ...state, number: state.number + 1 }; }, minus(state) { return { ...state, number: state.number - 1 }; }, }, effects: { *asyncAdd(action, { call, put }) { yield call(delay, 1000); yield put({ type: 'add' }); }, }, }; // count.js import React, { useState, useEffect } from 'react'; import { useModel } from 'dva'; const Count = () => { const [state, dispatch] = useModel(state => [state.count, { add: () => dispatch({ type: 'add' }) }]); useEffect(() => { // 如果需要,这里可以发起副作用操作 // 可以通过model中的effects来处理异步操作 }, []); return ( <div> <p>Count: {state.number}</p> <button onClick={() => dispatch({ type: 'minus' })}>-</button> </div> ); }; // App.js import React from 'react'; import { connect } from 'dva'; import Count from './components/Count'; const App = ({ count }) => { return ( <div> <Count /> </div> ); }; const mapStateToProps = state => ({ count: state.count, }); export default connect(mapStateToProps)(App); ``` 在这个示例中,我们定义了一个包含 `namespace`、`state`、`reducers` 和 `effects` 的模型。在函数组件 `Count` 中,我们使用 `useModel` 钩子来获取状态和派发动作。在类组件 `App` 中,我们使用 `connect` 高阶组件来将状态映射到组件的 props 中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值