React 中级三部曲 | 8月更文挑战

350 阅读9分钟

第四节: React组件通讯

1, React组件通讯介绍

组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据,一个完整的功能拆分成多个组件,而在这个过程中,多个组件之间不可避免的要共享数据,为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯

2,组件的props

  • 组件是封闭的,要接受外部的数据应该通过props接收
  • props的作用:接收传递给组件的数据
  • 传递数据: 给组件标签添加属性
  • 接收数据: 函数组件通过参数props接收数据,类组件通过this.props接收
<Hello name="java" age={10} />
function Hello(props) {
  console.log(props)
  return (
    <div>{props.name}</div>
  )
}
class Hello extends React.Component {
    render() {
      return (
        <div>接收到的数据:{ this.props.age }</div>
      )
    }
}
<Hello name="java" age={19} />

(1)可以给组件传递任意类型的数据

特点

ReactDOM.render(
  <Hello2 
   name="zlm"
   age={19}
   colors={['red', 'green', 'blue']}
   fn={() => console.log('这是一个函数')}
   tag={<p>这是一个P标签</p>}
  />,
  document.getElementById('root')
);

(2)props是只读的对象,只能读取属性的值,无法修改对象

(3)注意:使用类组件时,如果写了构造函数,应该将props传递给super(), 否则,无法在构造函数中获取到props, 其实在render()中是可以拿到this.props值的

推荐写法:直接在construtor中传递props \

class Props extends React.Component {
    constructor(props) {
        super(props) // 推荐将props传递给父类构造函数
    }

    render() {
        return <div>{this.props.age}</div>
    }
}

3, 组件通讯的三种方式

一,父组件传递数据给子组件

1,父组件提供要传递的state数据

2,给子组件标签添加属性,值为state中的数据

3,子组件中通过props接收父组件中传递的数据

class Father extends React.Component {

    state = { lastName: 'zlm' }

    render() {

        return (
            <div>
                传递给子组件:<Child name={ this.state.lastName } />
            </div>
        )
    }
}
function Child(props) {
   return (
    <div>
      子组件接收到的数据{ props.name }
    </div>
   )
}

思路:利用回调函数,父组件提供回调函数,子组件调用,将要传递的数据作为回调函数的参数

二,子组件传递数据给父组件 1, 父组件提供一个回到函数(用于接收数据)

import React from 'react'
class Father extends React.Component {
    state = { lastName: 'zlm' }
    // 回调函数,用了接收数据
    getChildMsg = (data) => {
        console.log('接收到子组件中传递过来的数据', data)
        this.setState({
            lastName: data
        })
    }
    render() {
        return (
            <div>
                父组件:{this.state.lastName}
                <Child name={ this.state.lastName } getMsg={this.getChildMsg} />
            </div>
        )
    }
}
​
// 子组件
class Child extends React.Component {
    state = { msg: '书都有'}
    handleClick = () => {
        this.props.getMsg(this.state.msg)
    }
   render()  {
       return (
           <div>
               子组件: <button onClick={this.handleClick}>点我,传递给父组件数据</button>
           </div>
       )
   }
}
export default Father

三,兄弟组件通讯

1, 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态

2,父组件提供共享状态和共享方法

3,要通讯的子组件只需通过props接收状态或操作状态

class Counter extends React.Component {
    state = {
        count: 0
    }
    onIncrement = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
            <div>
                <Child1 count={this.state.count}/>
                <Child2 onIncrement = {this.onIncrement}/>
            </div>
        )
    }
}
const Child1 = (props) => {
   return <h1>计数器:{props.count}</h1>
}
const Child2 = (props) => {
    return <button onClick={()=> props.onIncrement()}>+1</button>
}

4, Context (跨组件传递数据)

使用步骤

1,调用React.createContext()创建Provider(提供数据)和Consumer(消费数据),两个组件

const { Provider, Consumer } = React.createContext()
<Provider>
  <div className="App">
    <Child1 />
  </div>
</Provider>
<Provider value="pink" >
<Consumer>
  { (data) => <span>要接受的数据{ data }</span> }
</Consumer>
import React from 'react'// 创建两个组件
const { Provider, Consumer } = React.createContext()
​
class App extends React.Component {
    render() {
        return (
          <Provider value="pink">
            <div className="app">
               <Node />
            </div>
          </Provider>
        )
    }
}
​
const Node = (props) => {
    return (
        <div className="node">
           <SubNode />
        </div>
    )
}
const SubNode = (props) => {
    return (
        <div className="subnode">
            <Child />
        </div>
    )
}
​
const Child = () => {
    return (
        <div className="child">
          <Consumer>
              {
                  data => <span>子组件拿到的:{data}</span>
              }
          </Consumer>
        </div>
    )
}
​
export default App;
functin Hello(props) {
   return (
     <div>
       组件子节点: {props.children}
     </div>
   )
}
<Hello>我是子节点</Hell0>
import React from 'react'
import ReactDOM from 'react-dom'
const MyApp = props => {
  // 组件标签的子节点是函数的获取方式
  props.children()
  return (
      <div>
          <h1>组件标签的子节点,下面是文本,元素,的获取方式</h1>
          {props.children}
      </div>
  )
}
ReactDOM.render(
    <MyApp>
      {
       () => console.log('这是一个函数节点')
      }
    </MyApp>,
    document.getElementById('root')
)
App.propTypes = {
   colors: PropTypes.array
}
npm i prop-types
import PropTypes from 'prop-types'

Hello.propTypes = {
   age: PropTypes.number
}
// 常见类型
optionalFunc: PropTypes.func,
​
// 必须按
requiredFunc: PropTypes.func.isRequired
​
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
  color: PropTypes.string,
  fontsize: PropTypes.number
})
App.propTypes = {
    a: PropTypes.number,
    fn: PropTypes.func.isRequired,
    tag: PropTypes.element,
    filter: PropTypes.shape({
        area: PropTypes.string,
        price: PropTypes.number
    })
}
  • 常见类型: array, bool, func, number, object, string
  • React 元素类型: element
  • 必填项: isRequired
  • 特定结构的对象: shape({})

常见的props校验规则:

(4)校验规则通过 PropTypes对象来指定

(3) 使用 组件名.propTypes = {} 来给组件的props添加校验规则

(2) 导入prop-types包

(1)安装 prop-types

使用该功能的步骤:

3,props校验: 允许在创建组件的时候,就指定props的类型,格式

2,children属性与普通的props一样,值可以是任意值,文本,元素,组件,函数

1,children属性: 表示组件标签的子节点。当组件标签有子节点的时候,props就会有该属性

5, props 深入

Consumer用来消费数据的,通过回调函数的参数接收value值

Provider用来提供数据的,通过value,

总结:Context提供了两个组件,Provider和Consumer,

DEMO

4, 调用Consumer组件接收数据

3,设置value属性,表示要传递的数据

2,使用Provider组件作为父节点

第五节:组件的生命周期

1,组件的生命周期概述

为什么要理解组件的生命周期?

意义:组件的生命周期有助于理解组件的运行方式,完成更负责的组件功能,分析组件错误原因

组件的生命周期: 组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程

钩子函数: 生命周期的每一个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数

钩子函数的作用: 为开发人员在不同阶段操作组件提供了时机

注意: 只有类组件才有生命周期

2,生命周期的三个阶段

1, 创建时 2,更新时 3,卸载时

(1) 每个阶段的执行时机

(2)每个阶段钩子函数的执行顺序

(3) 每个阶段钩子函数的作用

  • 创建时 (挂载阶段)

    执行时机:组件创建时,页面加载时

    执行顺序:

    constructor() —> render() —> componentDidMount

钩子函数触发时机作用
constructor创建组件时,最新秩序1,初始化state 2,为事件处理程序绑定this
render每次组件渲染都会触发渲染UI,(注意不能调用setState())
componentDidMount组件挂载(完成DOM渲染)后1,发送网络请求 2,DOM操作

为什么不能在render()里面调用setState() ?

因为我们知道,setState是用来更新state里面的状态的,一旦state里面的状态发生了改变,就会更新UI,如果在
render()里面调用this.setState(),会发生递归现象
  • 更新时

执行时机:1, 接收new props, 2,setState(), 3,forceUpdate() 说明: 以上三者任意一种变化,组件就会重新渲染 执行顺序: render() —> componentDidUpdate()

钩子函数触发时机作用
render每次组件渲染都会触发渲染UI,与挂载阶段是同一个render
componentDidUpdate组件更新(完成DOM渲染)后1,发送网络请求 2,DOM操作 3,注意:如果要设置setState(), 必须要放在一个if 条件中

为什么在componentDidUpdate里面调用setState()必须要放在if条件中?

因为render()方法执行完之后,就开始执行componentDidUpdate, 如果在setState里面更新了state状态,会
导致render()重新执行,render()执行完之后,componentDidUpdate又继续执行,所以会导致递归现象
if(prevProps.count !== this.props.count) {
     this.setState({})
     // 发送ajax请求的代码
 }
  • 卸载时

  • 执行时机: 组件从页面中消失

    钩子函数触发时机作用
    componentWillUnmount组件卸载(从页面中消失)执行清理工作,比如:清理定时器

不常用的钩子函数

已经废弃的钩子函数:

componentWillMount
componentWillReceiveProps
componentWillUpdate

新增的钩子函数(但都不常用)

getDerivedStateFromProps
getSnapshotBeforeUpdate
shouldComponentUpdate(常用)

3, 总结

1,代码优化,推荐给 render props模式添加props校验

Mouse.propTypes = {
  children: PropTypes.func.isRequired
}

2,应该在组件卸载时,解除mousemove事件绑定

componentWillUnmount() {
   window.removeEventListener('mousemove', this.handleMouseMove)
}

3,DEMO

import React from 'react'class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
    console.log('生命周期钩子函数: constructor')
  }
  componentDidMount() {
    console.log('生命周期钩子函数: componentDidMount')
  }
  handleClick = () => {
      this.setState({
        count: this.state.count + 1
      })
      // this.forceUpdate() 强制更新
  }
  render() {
    console.log('生命周期钩子函数: render')
    return (
      <div>
        {
          this.state.count > 3 ?
          <p>豆豆被打死了</p> : 
          <Counter count={this.state.count}/>
        }
         <button onClick={this.handleClick}>打豆豆</button>
      </div>
    )
  }
​
}
class Counter extends React.Component {
  componentDidMount() {
    // 开启定时器
    this.timerId = setInterval(() => {
      console.log('子组件----定时器开始执行')
    })
  }
  // 注意:如果要调用setState(), 必须放在一个if中
  componentDidUpdate(prevProps) {
    console.log('子组件----生命周期钩子函数: componentDidUpdate')
    // 获取DOM操作
    const title = document.getElementById('title')
    console.log(title.innerHTML)
    console.log('上一次的props:', prevProps, '当前的props:', this.props)
    if(prevProps.count !== this.props.count) {
       this.setState({})
       // 发送ajax请求的代码
    }
  }
  render() {
    console.log('子组件----生命周期钩子函数: render')
    return <h1 id="title">统计豆豆被打次数{this.props.count}</h1>
  }
  componentWillUnmount() {
    console.log('子组件----生命周期钩子函数: componentWillUnmount')
    // 清理定时器
    clearInterval(this.timerId)
  }
}
export default App;
​

第六节:React 高阶组件

1, 高阶组件

目的: 实现状态逻辑复用

采用包装模式:高阶组件就相当于手机壳,给手机提供保护功能,通过包装组件,增强组件功能。

思路分析

  • 高阶组件(HOC, Highter-Order-Component)就是一个函数, 接收要包装的组件,返回增强后的组件
  • 高阶组件内部创建一个类组件,这个类组件中提供服用状态的逻辑代码,通过prop将复用的状态传递给被包装组件
const EnComp = withHOC(wrapComponet)

创建高阶组件步骤

(1)创建一个函数,名称约定以with开头

(2)指定函数参数,参数应该以大写字母开头(参数是要渲染的组件)

(3)在函数内部创建一个类组件,提供复用的逻辑代码,并返回

(4)在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件

(5)调用高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中

(6) 高阶组件设置: displayName, 使用高阶组件存在的问题,得到的两个组件名称相同,原因是默认情况下,为高阶组件设置displayName便于区分不同的组件

import React from 'react'import img from './logo.svg'// 创建高阶组件
 function withMouse(WrappedComponent) {
    class Mouse extends React.Component {
      state = {
        x: 0,
        y: 0
      }
      handleMouseMove = e => {
        this.setState({
          x: e.clientX,
          y: e.clientY
        })
      }
      // 控制监听鼠标状态逻辑
      componentDidMount() {
        window.addEventListener('mousemove', this.handleMouseMove)
      }
      componentWillUnmount() {
        window.removeEventListener('mousemove', this.handleMouseMove)
      }
      render() {
      return <WrappedComponent {...this.state}></WrappedComponent>
      }
    }
    // 设置displayName
    Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
    return Mouse;
 }
 // 测试高阶组件
 const Position = props => (
   <p>
      鼠标当前位置: (x: {props.x}, y: {props.y})
   </p>
 )
​
 const Cat = props => (
   <img 
     src={img}
     alt=""
     style={{
       position: 'absolute',
       width: '35px',
       height: '35px',
       top: props.y - 60,
       left: props.x - 60
     }}
   />
 )
  function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component'
  }
 // 获取增强后的组件
 const MousePositon = withMouse(Position)
 
 // 调用高阶组件来增强组件功能
 const MouseCat = withMouse(Cat)
​
 
class App extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <div>
         <MousePositon></MousePositon>
         <MouseCat></MouseCat>
      </div>
    )
  }
}

(7)高阶组件 传递props丢失问题?

因为高阶组件没有往下传递props

解决方式: 渲染WrappedComponent时,将state和this.props一起传递给组件

 return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>

2, 总结

React组件进阶

1, 组件通讯是构建React应用必不可少的一环

2, props的灵活性让组件更加强大

3, 状态提升是react组件的常用模式

4, 组件生命周期有助于理解组件的运行过程

5, 钩子函数让开发者可以在特定的时机执行某些功能

6, render props模式和高阶组件都可以实现组件状态逻辑复用

7, 组件极简模式: (state, props) => UI