组件通讯之props,context
一、组件通讯介绍
- 组件是独立封闭的单元
- 通常一个完整的页面是由多个组件组合而成的
- 为了实现组件之间共享一些数据的功能,所以就需要组件之间能够通讯,这个过程就是
组件通讯
二、组件的props
1、props的作用 :用于接收外界向组件中传递的数据
传递数据:渲染组件时,在组件标签中使用自定义属性向组件内部传递数据
接收数据:组件内部可以使用 props 来接收自定义属性传递的数据
- 函数组件通过
参数props
接收数据 - 类组件通过
this.props
接收数据
2、props的特点:
- 除了字符串之外,其他类型的数据都需要使用 {}
- props 是只读属性,不能修改里面的数据
- 使用类组件时,如果写了构造函数,应该将 props 传递给 super(),否则在 constructor 中无法使用props(其他方法可以使用),
推荐使用props作为constructor的参数!!
3、函数组件中接收自定义属性中的数据
import React from 'react'
import ReactDOM from 'react-dom'
//通过props来接收数据
const Hello = (props)=>{
//props是一个对象
console.log(props);
return(
<div>
<h1>我的名字是:{props.name}</h1>
</div>
)
}
//传递数据
ReactDOM.render(<Hello name='jack' />, document.getElementById('root'))
4、类组件中接收自定义属性中的数据
类组件方式1
import React from 'react'
import ReactDOM from 'react-dom'
class Hello extends React.Component{
render(){
console.log(this.props); //this.props是存储数据的对象
return(
<div>
<h1>我的名字是:{this.props.name},今年{this.props.age}岁了</h1>
</div>
)
}
}
//传递数据
ReactDOM.render(<Hello name='anni' age={19}/>, document.getElementById('root'))
调用组件时,使用自定义属性将数据传递到组件内部
this.props.name this,props.age
类组件方式2
如果写了构造函数,则需要将props传递给super(),否则在constructor中无法使用props(其他方法可以使用)
import React from 'react'
import ReactDOM from 'react-dom'
class Hello extends React.Component{
constructor(props){
super(props)
}
render(){
return(
<div>
<h1>我的名字是:{this.props.name},今年{this.props.age}岁了</h1>
</div>
)
}
}
//传递数据
ReactDOM.render(<Hello name='anni' age={19}/>, document.getElementById('root'))
三、组件通讯的三种方式
1、父组件传递数据给子组件
实现要点:
- 父组件提供state,并且内部设置好传递给子组件的数据
- 父组件调用子组件时,为组件标签添加自定义属性,值为 state 中的数据
- 子组件中通过 props 接收父组件中传递的数据
import React from 'react'
import ReactDOM from 'react-dom'
import './css/b.css'
// 目标: 子组件接收到父组件的数据
class Father extends React.Component {
state = {
name: 'anni',
age: 18
}
render () {
return (
<div className="father" class="father">
我是父组件
{/* 父组件在调用子组件时,将数据通过自定义属性传递给子组件 */}
<Son name={this.state.name} age={this.state.age} />
</div>
)
}
}
class Son extends React.Component {
render () {
return (
<div className="son" class="son">
我是子组件<br />
{/* 子组件使用 props 来接收父组件数据 */}
接收到父组件的数据为: {this.props.name} - {this.props.age}
</div>
)
}
}
//传递数据
ReactDOM.render(<Father/>, document.getElementById('root'))
2、子组件传递数据给父组件
核心: 利用回调函数
父组件
- 父组件提供一个回调函数用于接收子组件的数据
- 父组件调用子组件时,将函数作为参数传给子组件
子组件 (子组件已经将父组件函数保存在 props 中)
- 子组件中使用 state 定义要传递给父组件的数据
- 子组件利用事件调用父组件传递的函数,并将数组作为参数传入
import React from 'react'
import ReactDOM from 'react-dom'
import './css/b.css'
// 目标: 将子组件中的数据传递到父组件中
class Father extends React.Component {
//提供回调函数,用来接收数据
getChildData = (data) =>{
console.log("getChildData:",data)
}
render () {
return (
<div className="father">
<div>我是父组件</div>
<Son name="zs" age={18} fn={this.getChildData}/>
</div>
)
}
}
class Son extends React.Component {
state = {
name: '小明',
age: 18
}
handleClick = () =>{
console.log('click')
console.log(this.props.name)
this.props.fn(this.state)
}
render () {
return (
<div className="son">
<div>我是子组件</div>
<button onClick={this.handleClick}>发送</button>
</div>
)
}
}
//传递数据
ReactDOM.render(<Father/>, document.getElementById('root'))
3、兄弟组件传值
思路: 状态提升
状态提升:将数据统一放在父组件中进行保存,父组件既要提供state保存数据(提供共享状态),也要提供修改状态的方法,让子组件能向父组件进行传值
- 子组件1 通过调用父组件的函数将数据传递给父组件 (子向父传值)
- 父组件再通过子组件属性向子组件2传值 (父向子传值)
import React from 'react'
import ReactDOM from 'react-dom'
import './css/b.css'
class App 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 = () => {
return <button onClick = {()=>{this.onIncrement()}}>+1</button>
}
//传递数据
ReactDOM.render(<App/>, document.getElementById('root'))
4、Context
传统方式缺陷明显,一层一层传递太过复杂,使用 Context 便可以实现跨组件传值
(比如主题,语言等)
实现步骤:
- 调用 React.createContext() 方法创建 Provider (提供数据) 和 Consumer (消费数据)
- 使用 Provider 组件作为父节点,并使用 value属性传递数据
- 使用 Consumer 接收数据
import React from 'react'
import ReactDOM from 'react-dom'
import './css/b.css'
//1. 调用 React.createContext() 方法创建 Provider 和 Consumer 两个组件
const {Provider, Consumer} = React.createContext()
class App extends React.Component {
state = {
msg: '我是 App.state.msg 的数组'
}
render () {
return (
//2. 使用 Provider 组件包裹整个组件, 并使用 value 属性传递数据
<Provider value={this.state.msg}>
<div className="app">
App组件
<Child />
</div>
</Provider>
)
}
}
const Child = () => {
return (
<div className="child">
Child组件
<SubChild />
</div>
)
}
const SubChild = () => {
return (
<div className="subchild">
SubChild组件:
//3. 使用 Consumer 组件来接收 Provider 提供的数据
<Consumer>
{data => <span>{data}</span>}
</Consumer>
</div>
)
}
//传递数据
ReactDOM.render(<App/>, document.getElementById('root'))
效果如图:实现了跨层传输数据
四、props 深入
1、children 属性
- render 方法在渲染组件标签时可以使用双标签
- 组件使用双标签时,内部可以填入子节点,例如:文本子节点、标签子节点、JSX子节点、函数子节点
- props.children 中以数组形式保存这些子节点
import React from 'react'
import ReactDOM from 'react-dom'
//children为JSX
const Test = ()=> <button>我是button组件</button>
//children为文本
const App = props =>{
console.log(props);
props.children()
return(
<div>
<h1>组件标签的子节点:</h1>
{props.children}
</div>
)
}
ReactDOM.render(<App>
{/* <p>我是一个子节点,是一个p标签</p>
<Test/> */}
{/* 函数子节点 */}
{
()=> console.log('这是一个函数子节点')
}
</App>, document.getElementById('root'))
2、props 校验
props用来接收外来数据,这些数据可以在组件中被使用,但是组件的调用者可能会传入一些不符合规则的数据,当组件调用者传入不符合规则的数据时,通过props 校验进行错误提示从而更快找到问题。
props 校验:是允许在创建组件的时候,就指定 props 的类型、格式在用户调用组件出现错误时产生的原因。
使用步骤:
1. 安装 prop-types: npm i prop-types
2. 导入 prop-types 包
3. 为组件添加propTypes验证
//1. 导入 prop-types 包
import PropTypes from 'prop-types'
//2. 使用组件名.propTypes = {}来为组件的props添加校验规则,校验规则由PropTypes对象来指定
App.propTypes = {
color: PropTypes.array
}
3、常见校验规则
- 常见数据类型: array、bool、func、number、object、string
- React元素类型: element(props.element)
- 必填项: isRequired
- 特定结构: shape({})
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
class App extends React.Component {
render () {
console.log(this.props)
return (
<div>你好外星人</div>
)
}
}
// 属性 a 类型为数值
// 属性 fn 类型为函数
// 属性 tag 类型为元素
// 属性 firends 类型为 对象 {name: 'anni', age: 18}
App.propTypes = {
a: PropTypes.number,
fn: PropTypes.func.isRequired,
sum: PropTypes.number,
firends: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number
})
}
ReactDOM.render(
<App a={18} fn={()=>{}} sum={1+2} firends={{name:'anni', age:18}} />,
document.getElementById('root')
)
4、Props默认值
调用组件时可以为组件设置默认值
如果调用组件时设置了属性值则使用属性值,没有设置属性值则使用默认属性值
import React from 'react'
import ReactDOM from 'react-dom'
class Pagination extends React.Component {
render () {
return (
<div>
每页显示数量: {this.props.pagesize}
<br/>
当前显示第{this.props.pagenum}页
</div>
)
}
}
// 设置默认值
Pagination.defaultProps = {
pagenum: 1,
pagesize: 10
}
ReactDOM.render(
<Pagination pagenum={3} />,
document.getElementById('root')
)