React 组件之间到底是如何进行通讯的?
React 组件进阶——组件通讯
- 目录
- 组件通讯介绍
- 组件的 props
- 组件通讯的三种方式
- 父组件传递数据给子组件
- 子组件传递数据给父组件
- 兄弟组件
- Context
- props 深入
- children 属性
- props 校验
- props 的默认值
目录
组件通讯介绍
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好地完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯。
组件的 prop
- 组件是封闭的,要接收外部数据应该通过 props 来实现。
- props 的作用:接收传递给组件的数据。
- 传递数据:给组件标签添加属性。
<hello name="jack" age={19} />
4.接收数据:函数组件通过参数 props 接收数据,类组件通过 this.props 接收数据。
// 函数组件接收数据 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="jack" age={19} />
特点
- 可以给组件传递任意类型的数据。
- props是只读的对象,只能读取属性的值,无法修改对象。
- 注意:使用类组件时,如果写了构造函数,**应该将 props 传递给 super() **,否则,无法在构造函数中获取到 props!
class Hello extends React.Component { constructor(props){ // 推荐将 props 传递给父类构造函数 super(props) } render() { return ( <div>接收到的数据:{this.props.age}</div> ) } } <hello name="jack" age={19} />
组件通讯的三种方式
父组件传递数据给子组件
- 父组件提供要传递的 state 数据。
- 给子组件标签添加属性,值为 state 中的数据。
- 子组件中通过 props 接收父组件中传递的数据。
class Hello extends React.Component { state = { lastName: '王' } render() { return ( <div>传递给子组件:<Child name={this.state.lastName}></Child></div> ) } } // 子组件 function Child(props) { return <div>子组件接收到的数据:{props.name}</div> }
子组件传递数据给父组件
- 思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
- 父组件提供一个回调函数(用于接收数据)。
- 将该函数作为属性的值,传递给子组件。
- 子组件通过 props 调用回调函数。
- 将子组件的数据作为参数传递给回调函数。
class Parent extends React.Components { // 获取子组件的函数 getChildMsg = (msg) => { console.log('接收到子组件的数据',msg) } render() { return ( <div> 子组件: <Child getMsg={this.getChildMsg} /> </div> ) } }
2.注意:回调函数中的 this 指向问题!!!
3.小案例介绍子组件向父组件传递数据:
class Parent extends React.Component { state = { parentMsg: '' } // 获取子组件的函数 getChildMsg = data => { console.log('接收到子组件的数据',data) this.setState({ parentMsg: data }) } render() { return ( <div style={{backgroundColor: 'red'}}> 父组件:{this.state.parentMsg} <div style={{backgroundColor: 'blue', color: '#fff'}}> 子组件: <Child getMsg={this.getChildMsg} /> </div> </div> ) } } class Child extends React.Component { state = { childMsg: '刷抖音' } handleClick = () => { this.props.getMsg(this.state.childMsg) } render() { return ( <div> <button onClick={this.handleClick}>点我向父组件传递数据</button> </div> ) } }
兄弟组件
- 没直接嵌套关系的两个组件是兄弟组件。
- 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态。
- 思想:状态提升。
- 公共父组件职责:
- 提供共享状态。
- 提供操作共享状态的方法。
- 要通讯的子组件只需通过 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> }
Context
- 思考:App 组件要传递数据给 Child 组件,该如何处理?
- 处理方式:使用 props 一层层组件往下传递(繁琐)。
- 更好的方式:使用 Context 。
- 作用:跨组件传递数据(比如:主题、语言等)。
- 使用步骤:
- 调用 React.createContext() 创建 Provider(提供数据)和 Consumer(消费数据)两个组件。
const { Provider, Consumer } = React.createContext()
使用 Provider 组件作为父节点。
<provider> <div> <Child1 /> </div> </provider>
设置 value 属性,表示要传递的数据。
<Provider value="pink" ></Provider>
调用 Consumer 组件接收数据。
<Consumer> {data => <span>data 参数表示接收到的数据 -- {data}</span>} </Consumer>
3.案例讲解:
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 ( <Consumer> { data => <div className="child">我是子节点--{data}</div> } </Consumer> ) }
- 总结:
- 如果两个组件是远房亲戚(比如,嵌套多层)可以使用 Context 实现组件通讯。
- Context 提供了两个组件:Provider 和 Consumer 。
- Provider 组件:用来提供数据。
- Consumer 组件:用来消费数据。
props 深入
children 属性
- children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性。
- children 属性与普通的 props 一样,值可以是任意值(文本、React 元素、组件,甚至是函数)。
const App = props => { console.log(props) // 输出的是:我是子节点。 return ( <div> <h1>组件标签的子节点:</h1> {props.children} </div> ) } ReactDOM.render(<Hello>我是子节点</Hello>, document.querySelector('#root')
props 校验
- 对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据。
- 如果传入的数据格式不对,可能会导致组件内部报错。
- 关键问题:组件的使用者不知道明确的错误原因。
- props 校验:允许在创建组件的时候,就指定 props 的类型、格式等。
- 作用:捕获使用组件时因为 props 导致的错误,给出明确的错误提示,增加组件的健壮性。
App.propTypes = { colors: PropTypes.array }
5.使用步骤:
- 安装包 prop-types(yarn add prop-types/npm i props-types)。
- 导入 prop-types 包。
- 使用
组件名.propTypes = {}
来给组件的 props 添加校验规则。 - 校验规则通过 PropTypes 对象来指定。
import PropTypes from 'prop-types' function App(props) { return ( <h1>Hi,{props.colors}</h1> ) } App.propTypes = { // 约定 colors 属性为 array 类型。 // 如果类型不对,则会报出明确错误,便于分析错误原因。 colors: PropTypes.array }
6.约束规则:
- 常见类型:array、bool、func、number、object、string。
- React 元素类型:element。
- 必填项:isRequired。
- 特定结构的对象:shape({ })。
// 常见类型 optionalFunc: PropTypes.func, // 必选 requiredFunc: PropTypes.func.isRequired, // 特定结构的对象 optionalObjectWithShape: Proptypes.shape({ color: PropTypes.string, fontSize: PropTypes.number })
props 的默认值
- 场景:分页组件 → 每页显示条数
- 作用:给 prpps 设置默认值,在为传入 props 时生效。
function App(props) { return ( <div>此处展示 props 的默认值:{props.pageSize}</div> ) } // 设置默认值 App.defaultProps = { pageSize: 10 } // 不传入 pageSize 属性 <App />