react组件通讯-生命周期- render props模式

组件通讯

组件的props

  • 组件是封闭的,要接收外部数据应该通过props来实现
  • props的作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性
  • 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据

特点:

  • 可以传递任意类型的数据
// 传递数据
ReactDOM.render(
  <Hello name='maomao1' age={19} colors={['red','blue']} fn={()=>{console.log('这是一个函数')}}
  tag='{<p>这是一个p标签</p>}'/>,
  document.getElementById('root')
);
  • props是只读对象,只能读取属性值,不能修改对象
  • 注意:如果在类组件中写了构造函数,sonstructor,应该将props传递给super(),否则无法获取props,
class Hello extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <div>
        <h1>props:{this.props.name}</h1>
      </div>
    )
  }
}
// 传递数据
ReactDOM.render(
  <Hello name='maomao1' />,
  document.getElementById('root')
);

函数组件


//接收数据
const Hello = (props) =>{
  return (
    <div>
      <h1>props:{props.name}</h1>
    </div>
  )
}
// 传递数据
ReactDOM.render(
  <Hello name='maomao'/>,
  document.getElementById('root')
);

类组件

class Hello extends React.Component {
  render() {
    return (
      <div>
        <h1>props:{this.props.name}</h1>
      </div>
    )
  }
}
// 传递数据
ReactDOM.render(
  <Hello name='maomao1' />,
  document.getElementById('root')
);

props深入

children属性

  • 表示组件标签里面的子节点,当组件标签里面有子节点时,props就有children属性
  • children属性的值与props一样
import React from 'react'
import ReactDOM from 'react-dom'

/* 
  children 属性
*/

const App = props => {
    
    
  console.log(props)
  props.children()

  return (
    <div>
      <h1>组件标签的子节点:</h1>
      {
    
    /* {props.children} */}
    </div>
  )
}

ReactDOM.render(
  <App>{
    
    () => console.log('这是一个函数子节点')}</App>,
  document.getElementById('root')
)

// children为:jsx或组件
// const Test = () => <button>我是button组件</button>
// const App = props => {
    
    
//   console.log(props)
//   return (
//     <div>
//       <h1>组件标签的子节点:</h1>
//       {props.children}
//     </div>
//   )
// }

// ReactDOM.render(
//   <App>
//     {/* <p>我是子节点,是一个p标签</p> */}
//     <Test />
//   </App>,
//   document.getElementById('root')
// )

// children为:文本节点
/* const App = props => {
  console.log(props)
  return (
    <div>
      <h1>组件标签的子节点:</h1>
      {props.children}
    </div>
  )
}

ReactDOM.render(<App>我是子节点</App>, document.getElementById('root')) */


props校验

  • 对于组件来说,props是外来的,无法保证传入的是什么格式的数据
  • 传入的数据格式不对,可能导致组件内部错误
  • 关键是组件使用者不知道为啥报错
作用:
  • 允许在创建组件的时候,指定props的类型和格式
  • 作用:捕获组件使用时因为使用props导致的错误,能够给出明确的错误提示,增加组件的健壮性
使用步骤
  • 安装prop-types (yarn add prop-types / npm i prop-types)
  • 导入prop-types包
  • 使用:组件名.propTypes = {} 来给组件中的props添加校验
import React from 'react'
import ReactDOM from 'react-dom'

/* 
  props校验
*/

import PropTypes from 'prop-types'

const App = props => {
    
    
  const arr = props.colors
  const lis = arr.map((item, index) => <li key={
    
    index}>{
    
    item}</li>)

  return <ul>{
    
    lis}</ul>
}

// 添加props校验
App.propTypes = {
    
    
  colors: PropTypes.array
}

ReactDOM.render(
  <App colors={
    
    ['red', 'blue']} />,
  document.getElementById('root')
)


  • 约束规则:
    1.常见类型:array,bool,func,number,object,string
    2.react元素类型:element
    3.必填项:isRequired 例如:PropTypes.array.isRequired
    4.特定结构的对象:shape({})
import React from 'react'
import ReactDOM from 'react-dom'

/* 
  props校验
*/

import PropTypes from 'prop-types'

const App = props => {
  return (
    <div>
      <h1>props校验:</h1>
    </div>
  )
}

// 添加props校验
// 属性 a 的类型:      数值(number)
// 属性 fn 的类型:     函数(func)并且为必填项
// 属性 tag 的类型:    React元素(element)
// 属性 filter 的类型: 对象({area: '上海', price: 1999})
App.propTypes = {
  a: PropTypes.number,
  fn: PropTypes.func.isRequired,
  tag: PropTypes.element,
  filter: PropTypes.shape({
    area: PropTypes.string,
    price: PropTypes.number
  })
}

ReactDOM.render(<App fn={() => {}} />, document.getElementById('root'))


  • 组件的默认值
import React from 'react'
import ReactDOM from 'react-dom'

/* 
  props校验
*/

import PropTypes from 'prop-types'

const App = props => {
    
    
  return (
    <div>
      <p>组件的默认值:{
    
    props.pageSize}</p>
    </div>
  )
}

// 添加props校验
App.defaultTypes = {
    
    
  pageSize:10
}

ReactDOM.render(
  <App pageSize={
    
    20}/>,
  document.getElementById('root')
)

组件通讯

父->子

Child.js 文件

import React from "react";


//创建类组件
class Child extends React.Component {
    
    
  constructor(props){
    
    
    super(props) 
  }
  render() {
    
    
    return (
      <div>
        <p>子组件,接收父组件的数据:{
    
    this.props.name}</p>
      </div>
    )
  }
}
export default Child

Parent.js


import React from "react";
import Child from './Child'

//创建类组件
class Parent extends React.Component {
    
    

  state = {
    
    
    listName:'王'
  }
  constructor(){
    
    
    super() 
  }
  render() {
    
    
    return (
      <div>
        父组件:
        <Child name={
    
    this.state.listName}/>
      </div>
    )
  }
}
export default Parent

子->父

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

  • 父组件里面提供一个回调函数用于接收数据
  • 将该函数作为属性值传递给子组件
import React from "react";
import Child from './Child'

//创建类组件
class Parent extends React.Component {

  state = {
    listName:'王'
  }
  constructor(){
    super() 
  }
  getChiledMsg = (msg)=>{
console.log('----',msg)
  }
  render() {
    return (
      <div>
        父组件:
        <Child name={this.state.listName} getMsg = {this.getChiledMsg}/>
      </div>
    )
  }
}
export default Parent
  • 子组件通过props调用回调函数
    Parent.js
import React from "react";
import Child from './Child'

//创建类组件
class Parent extends React.Component {
    
    

  state = {
    
    
    listName: '王'
  }
  constructor() {
    
    
    super()
  }
  getChiledMsg = (msg) => {
    
    
    this.setState({
    
     listName: msg })
    console.log('----', msg)
  }
  render() {
    
    
    return (
      <div>
        父组件:
        <Child name={
    
    this.state.listName} getMsg={
    
    this.getChiledMsg} />
      </div>
    )
  }
}
export default Parent

Child.js


import React from "react";


//创建类组件
class Child extends React.Component {
    
    
  state = {
    
    
    childMsg:'react'
  }
  constructor(props){
    
    
    super(props) 
  }
  handleClick = ()=>{
    
    
    this.props.getMsg(this.state.childMsg)
  }
  render() {
    
    
    return (
      <div>
        <p>子组件,接收父组件的数据:{
    
    this.props.name}</p>
        <button onClick={
    
    this.handleClick}>子传父</button>
      </div>
    )
  }
}
export default Child

兄弟传递

  • 将共享状态提升到最近的公共父组件,又公共父组件去管理这个状态
  • 思想:状态提升
  • 公共父组件的职责:1.提供共享状态,2.提供操作共享状态的方法
  • 要通讯的子组件,只需要通过props去接受状态或者操作状态方法就可以
class Counter extends React.Component {
    
    

  state = {
    
    
    count:0
  }
  constructor() {
    
    
    super()
  }
  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>
}
// 传递数据
ReactDOM.render(
  <Counter />,
  document.getElementById('root')
);

跨组件传递-Context

  • 跨组件传递数据,不用像使用props一样一层一层传递
  • 使用步骤

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

// 创建context得到两个组件
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 = props => {
    
    
  return (
    <div className="child">
      <Consumer>
        {
    
    data => <span>我是子节点 -- {
    
    data}</span>}
      </Consumer>
    </div>
  )
}

// 传递数据
ReactDOM.render(
  <App />,
  document.getElementById('root')
);


  • 如果两个组件是嵌套层级比较多的话,就调用React.createContext创建Provider(提供数据)与Consunmer(消费数据)两个组件,

组件的生命周期

  • 意义,组件的生命周期有助于帮助我们理解组件的运行方式,完成更复杂的组件功能,分析组件的错误原因
  • 组件从被创建挂载在页面上运行到不用被卸载的过程
  • 钩子函数:生命周期在每个阶段伴随着一些方法的调用
  • 只有类组件才有生命周期

生命周期图

生命周期的三个阶段

创建时(挂载阶段)

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

更新时(更新阶段)

  • 执行时机:1. setState() 2. forceUpdate() 3. 组件接收到新的props
  • 说明:以上三者任意一种变化,组件就会重新渲染
  • 执行顺序:
    render() -> componentDidUpdate()
    | 钩子函数 | 触发时机 | 作用 |
    |--------|--------|
    | render | 每次组件渲染都会触发 |渲染UI(注意:不能调用setState()) |
    | componentDidUpdate | 组件更新(完成DOM渲染)后 | 1 发送网络请求 2 DOM操作 注意:如果要setState() 必须放在一个if条件中,否则也会导致递归更新 |
import React from 'react'
import ReactDOM from 'react-dom'

/* 
  组件生命周期
*/

class App extends React.Component {
  constructor(props) {
    super(props)

    // 初始化state
    this.state = {
      count: 0
    }
  }

  // 打豆豆
  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    })

    // 演示强制更新:
    // this.forceUpdate()
  }

  render() {
    console.warn('生命周期钩子函数: render')
    return (
      <div>
        <Counter count={this.state.count} />
        <button onClick={this.handleClick}>打豆豆</button>
      </div>
    )
  }
}

class Counter extends React.Component {
  render() {
    console.warn('--子组件--生命周期钩子函数: render')
    return <h1>统计豆豆被打的次数:{this.props.count}</h1>
  }
  componentDidUpdate(prevProps){
	  // 避免递归更新-比较两次前后更新deprops是否相同
	  console.log('上次的props',prevProps,'当前的props',this.props)
	  if(prevProps.count !== this.props.count){
		  this.setState({})
		  //发送ajax请求
	  }
  }
}

ReactDOM.render(<App />, document.getElementById('root'))


卸载时(卸载阶段)

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

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

render-props 与高阶组件

React组件复用概述

  • 思考:如果两个组件中的部分功能相似或相同,该如何处理?
  • 处理方式:复用相似的功能(联想函数封装)
  • 复用什么?1. state 2. 操作state的方法 (组件状态逻辑 )
  • 两种方式:1. render props模式 2. 高阶组件(HOC)  注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)

render props 模式

思路分析

  • 思路:将要复用的state和操作state的方法封装到一个组件中
  • 问题1:如何拿到该组件中复用的state?
  • 在使用组件时,添加一个值为函数的prop,通过 函数参数 来获取(需要组件内部实现)
  • 问题2:如何渲染任意的UI?
  • 使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)

使用步骤

  • 创建Mouse组件,在组件中提供复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
  • 将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
  • 使用 props.render() 的返回值作为要渲染的内容
import React from 'react'
import ReactDOM from 'react-dom'

/* 
  render props 模式
*/

// 导入图片资源
import img from './images/cat.png'

// 作用:鼠标位置复用
class Mouse extends React.Component {
  // 鼠标位置state
  state = {
    x: 0,
    y: 0
  }

  // 鼠标移动事件的事件处理程序
  handleMouseMove = e => {
    this.setState({
      x: e.clientX,
      y: e.clientY
    })
  }

  // 监听鼠标移动事件
  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove)
  }

  render() {
    return this.props.render(this.state)
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>render props 模式</h1>
        <Mouse
          render={mouse => {
            return (
              <p>
                鼠标位置:{mouse.x} {mouse.y}
              </p>
            )
          }}
        />

        {/* 猫捉老鼠 */}
        <Mouse
          render={mouse => {
            return (
              <img
                src={img}
                alt="猫"
                style={
   
   {
                  position: 'absolute',
                  top: mouse.y - 64,
                  left: mouse.x - 64
                }}
              />
            )
          }}
        />
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))


演示Mouse组件的复用

  • Mouse组件负责:封装复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
  • 状态:鼠标坐标(x, y)
  • 操作状态的方法:鼠标移动事件
  • 传入的render prop负责:使用复用的状态来渲染UI结构
class Mouse extends React.Component {
    
    
// … 省略state和操作state的方法
render() {
    
    
return this.props.render(this.state) } }
<Mouse render={
    
    (mouse) => <p>鼠标当前位置 {
    
    mouse.x}{
    
    mouse.y}</p>}/>

children代替render属性

  • 注意:并不是该模式叫 render props 就必须使用名为render的prop,实际上可以使用任意名称的prop
  • 把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式
  • 推荐:使用 children 代替 render 属性
<Mouse>
{({x, y}) => <p>鼠标的位置是 {x},{y}</p> }
</Mouse>
// 组件内部:
this.props.children(this.state)
// Context 中的用法:
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>

代码优化

  • 推荐:给 render props 模式添加 props校验

      Mouse.propTypes = {
        chidlren: PropTypes.func.isRequired
      }
    
  • 应该在组件卸载时解除 mousemove 事件绑定

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

猜你喜欢

转载自blog.csdn.net/wondermaomao/article/details/127699843