React学习笔记[7]✨~React父子组件间的通信👻

101 阅读5分钟

我正在参加「掘金·启航计划」

一、父组件向子组件传递数据

基本使用方法

父组件在展示子组件的时候,有时需要向子组件中传递数据:

父组件可通过 属性=值 的形式来向子组件传递数据

子组件通过 constructor 中的 props 参数来接收父组件传递过来的数据

比如在Main组件中使用了 Banner子组件及Header子组件等,在Main组件中获取到banner列表后传递给Banner组件,在Banner组件中进行展示:

Main.jsx

import React, { Component } from 'react'
import axios from "axios"

import MainBanner from './MainBanner'
import MainHeader from './MainHeader'

export class Main extends Component {
  constructor() {
    super()

    this.state = {
      banners: [],
    }
  }

  componentDidMount() {
    axios.get("http://123..").then(res => {
      const banners = res.data.data.banner.list
      this.setState({
        banners,
      })
    })
  }

  render() {
    const { banners } = this.state

    return (
      <div className='main'>
        <div>Main</div>
        <MainBanner banners={banners} title="轮播图"/>
        <MainHeader />
      </div>
    )
  }
}

export default Main

MainBanner.jsx:

import React, { Component } from 'react'
import PropTypes from "prop-types"

export class MainBanner extends Component {
  // 如果子组件中没有state,可以省略constructor
  // 因为组件中的constructor会自动将props放在组件实例上
  constructor(props) {
    super(props)
  }

  render() {
    const { title, banners } = this.props
    return (
      <div className='banner'>
        <h2>封装一个轮播图: {title}</h2>
        <ul>
          {
            banners.map(item => {
              return <li key={item.acm}>{item.title}</li>
            })
          }
        </ul>
      </div>
    )
  }
}
export default MainBanner

父组件传递过来的数据会被子组件的props接收,如果子组件不需要定义自己state则可以省略constructor

组件中的constructor默认会通过super(props)将props放到组件实例上

在子组件中可以通过this.props.xx来获取父组件中传递的数据

如果在父组件中想向子组件中传递多个属性,可以通过展开运算符的方式来传递:

import React, { Component } from 'react'
import Home from './Home'
export class App extends Component {
  constructor() {
    super()

    this.state = {
      info: { name: "zs", age: 18 }
    }
  }

  render() {
    const { info } = this.state

    return (
      <div>
        {/* 给Home传递数据 */}
        {/* <Home name={info.name} age={info.age}/> */}
        <Home {...info}/>
      </div>
    )
  }
}

export default App

PropTypes

对于传递给子组件的数据,有时候我们想要进行验证(尤其是对于大型项目来说):

  • 如果项目中使用了TypeScript,便可以直接进行类型验证
  • 但如果没有使用TypeScript,需要通过prop-types库来进行参数验证

比如可以验证数组,数组中包含哪些元素;

比如可以验证对象,对象中包含哪些key及value是什么类型;

比如可以确保没有传递某些数据时打印出警告信息;

也可以设置传入数据的默认值;

更多验证方式可参考官方文档:zh-hans.reactjs.org/docs/typech…

import React, { Component } from 'react'
import PropTypes from "prop-types"

export class MainBanner extends Component {
	//...
}

// MainBanner传入的props类型进行验证
MainBanner.propTypes = {
  banners: PropTypes.array,
  title: PropTypes.string,
  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,
}

// MainBanner传入的props的默认值
MainBanner.defaultProps = {
  banners: [],
  title: "默认标题"
}

export default MainBanner
import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 你可以将属性声明为 JS 原生类型,默认情况下
  // 这些属性都是可选的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  // (或 Fragment) 也包含这些类型。
  optionalNode: PropTypes.node,

  // 一个 React 元素。
  optionalElement: PropTypes.element,

  // 一个 React 元素类型(即,MyComponent)。
  optionalElementType: PropTypes.elementType,

  // 你也可以声明 prop 为类的实例,这里使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以让你的 prop 只能是特定的值,指定它为
  // 枚举类型。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 一个对象可以是几种类型中的任意一个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 可以指定一个数组由某一类型的元素组成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一个对象由某一类型的值组成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一个对象由特定的类型值组成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
  // 请不要使用 `console.warn` 或抛出异常,因为这在 `oneOfType` 中不会起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
  // 它应该在验证失败时返回一个 Error 对象。
  // 验证器将验证数组或对象中的每个值。验证器的前两个参数
  // 第一个是数组或对象本身
  // 第二个是他们当前的键。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

二、子组件向父组件传递消息

某些情况下,也需要子组件向父组件传递消息:

  • Vue中是通过自定义事件完成的
  • 而在React中,同样是通过props来传递父组件给子组件传递一个回调函数,子组件去调用这个函数从而实现子组件向父组件传递消息

案例

比如,在一个父组件中包含两个子组件,分别进行加数字与减数字的操作;而这个数字是在父组件中维护的,此时就要通过props向这两个子组件中传递回调函数来实现对父组件中数字的加、减:

App.jsx:

import React, { Component } from 'react'
import AddCounter from './AddCounter'
import SubCounter from './SubCounter'

export class App extends Component {
  constructor() {
    super()
    this.state = {
      counter: 100
    }
  }
  changeCounter(count) {
    this.setState({counter: this.state.counter + count})
  }
  render() {
    const { counter } = this.state
    return (
      <div>
        <div>{counter}</div>
        <AddCounter addCounter={(count) => this.changeCounter(count)}/>
        <SubCounter subCounter={(count) => this.changeCounter(count)}/>
      </div>
    )
  }
}

export default App

AddCounter.jsx:

import React, { Component } from 'react'

export class AddCounter extends Component {
  addCount(count) {
    this.props.addCounter(count)
  }
  render() {
    return (
      <div>
        <button onClick={() => this.addCount(1)}>Add 1</button>
        <button onClick={() => this.addCount(5)}>Add 5</button>
        <button onClick={() => this.addCount(10)}>Add 10</button>
      </div>
    )
  }
}

export default AddCounter

SubCounter.jsx:

import React, { Component } from 'react'

export class SubCounter extends Component {
  subCount(count) {
    this.props.subCounter(count)
  }
  render() {
    return (
      <div>
        <button onClick={() => this.subCount(-1)}>Sub 1</button>
        <button onClick={() => this.subCount(-5)}>Sub 5</button>
        <button onClick={() => this.subCount(-10)}>Sub 10</button>
      </div>
    )
  }
}

export default SubCounter

三、组件间通信案例

父组件中引入子组件TabControl,子组件TabControl中根据父组件传递的列表来进行渲染选择页签

  • 当点击不同页签时,页签高亮
  • 同时父组件中渲染出页签对应的内容

App.jsx:

import React, { Component } from 'react'
import TabControl from './TabControl'
export class App extends Component {
  constructor() {
    super()
    this.state = {
      titles: ['最新', '热门', '精选'],
      tabIndex: 0
    }
  }
  changeIndex(index) {
    this.setState({
      tabIndex: index
    })
  }
  render() {
    const {titles, tabIndex} = this.state
    return (
      <div>
        <TabControl titles={titles} tabClick={(index) => this.changeIndex(index)}/>
        { titles[tabIndex] }
      </div>
    )
  }
}

export default App

TabControl/index.jsx:

import React, { Component } from 'react'
import "./style.css"

export class TabControl extends Component {
  constructor() {
    super()
    this.state = {
      currentIndex: 0
    }
  }
  changeTab(index) {
    this.setState({currentIndex: index})
    this.props.tabClick(index)
  }
  render() {
    const { currentIndex } = this.state
    const { titles } = this.props
    return (
      <div className='tab-control'>
        {
          titles.map((tab, index) => {
            return (
              <div
                className={`item ${currentIndex === index ? 'active':''}`}
                key={tab}
                onClick={() => this.changeTab(index)}
              >
                <span className="text">{tab}</span>
              </div>
            )
          })
        }
      </div>
    )
  }
}

export default TabControl

style.css:

.tab-control {
  display: flex;
  align-items: center;
  height: 40px;
  text-align: center;
}

.tab-control .item {
  flex: 1;
}

.tab-control .item.active {
  color: red;
}

.tab-control .item.active .text {
  padding: 3px;
  border-bottom: 3px solid red;
}