目录
一、props基础
1.1 概述
1.2 函数组件通讯
1.2.1 基本用法
1.2.1 对象数据传递
1.3 类组件通讯
1.4 props的特点
二、组件通讯三种方式
2.1 父传子
2.2 子传父
2.3 兄弟组件通讯
三、context跨级组件
实现思路:
四、props进阶
4.1 children属性
4.2 props校验
4.2.1 类组件中
4.2.2 函数组件
4.3 约束规则
4.4 props默认值
4.4.1 类组件中使用props默认值
4.4.2 函数组件中使用props默认值
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。
在组件化过程中,我们将一个完整的功能 拆分成多个组件,以更好的完成整个应用的功能。
而在这个过程中,多个组件之间不可避免的要共享某些数据 。
为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通
组件是封闭的,要接收外部数据应该通过props来实现
props的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
子组件
import React from 'react'
export default function Props(props) {
console.log(props);
return (
标题:{props.title}
)
}
index.js--父组件
import Props from "./components/Props";
function App() {
return (
);
}
export default App;
如果子组件接收的数据是一个一个的字段,在对象中有很多数据需要传入,那么你可以使用es6中的扩展语法将对象的数据全部扩展进去
子组件Props.jsx
import React from 'react'
export default function Props(props) {
console.log(props);
return (
标题:{props.title}
姓名:{props.name}
年龄:{props.age}
地址:{props.address}
)
}
父组件App.jsx
import Props from "./components/Props";
let user = {
name: '张三',
age: 20,
address: '红旗河沟'
}
function App() {
return (
);
}
export default App;
如果子组件接收的是整个对象,那么你可以直接将对象传入
子组件Props.jsx
import React from 'react'
export default function Props(props) {
console.log(props);
return (
标题:{props.title}
姓名:{props.user.name}
年龄:{props.user.age}
地址:{props.user.address}
)
}
父组件App.jsx
import Props from "./components/Props";
let user = {
name: '张三',
age: 20,
address: '红旗河沟'
}
function App() {
return (
);
}
export default App;
类组件的外部数据通过this.props
来访问
子组件---接收数据
import React, { Component } from 'react'
export default class PropsClass extends Component {
constructor(props) {
super(props);
}
render() {
return (
标题:{this.props.title}
)
}
}
父组件--传递数据
import Props from "./components/Props";
function App() {
return (
);
}
export default App;
可以给组件传递任意类型的数据
props是只读的,不允许修改props的数据
Demo.js
// 函数组件
// 方式1
export default function Demo(props) { // 或下面这种写法--解构
// export default function Demo({car, money, check, name}) {
console.log(props);
return (
Demo组件
{
/**方式1
*
*/
}
{props.car}
{props.money}
{props.check ? '是' : '否'}
{props.name}
子组件:{props.money}
{
// 方式2
/**
* {car}
{money}
{check ? '是' : '否'}
{name}
*/
}
)
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
import Demo from './Demo'
class App extends Component {
state = {
money: 100
}
render() {
return
APP组件
{
/**
* 1. 通过属性的方式,给组件提供数据
* 2. 子组件中通过 props 就可以获得参数
*/
}
{console.log(1);}}
list={[1,2,3]}
obj = {{age: 12}}
content = {内容}
>
父组件:{this.state.money}
}
Add = () => {
this.setState({
money: this.state.money + 10
})
}
}
// 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
const element = (
);
// 参数1:渲染的 react 元素即虚拟 DOM
// 参数2:需要渲染到哪个容器中
const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);
注意:在类组件中使用的时候,需要把props传递给super(),否则构造函数无法获取到props
Hello.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import { Component } from 'react';
export default class Hello extends Component {
constructor(props) {
super(props)
this.state = {
money: this.props.money + 100
}
}
render() {
console.log(this.props);
return (
类组件
{this.state.money}
)
}
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
import Hello from './Hello'
class App extends Component {
state = {
money: 100
}
render() {
return
APP组件
{
/**
* 1. 通过属性的方式,给组件提供数据
* 2. 子组件中通过 props 就可以获得参数
*/
}
}
Add = () => {
this.setState({
money: this.state.money + 10
})
}
}
// 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
const element = (
);
// 参数1:渲染的 react 元素即虚拟 DOM
// 参数2:需要渲染到哪个容器中
const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);
父传子
子传父
非父子
父组件提供要传递的state数据
给子组件标签添加属性,值为 state 中的数据
子组件中通过 props 接收父组件中传递的数据
父组件提供数据并且传递给子组件
class Parent extends React.Component {
state = { lastName: '王' }
render() {
return (
传递数据给子组件:
)
}
}
子组件接收数据
function Child(props) {
return 子组件接收到数据:{props.name}
}
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
父组件提供一个回调函数(用于接收数据)
将该函数作为属性的值,传递给子组件
子组件通过 props 调用回调函数
将子组件的数据作为参数传递给回调函数
父组件提供函数
class Parent extends React.Component {
getChildMsg = (msg) => {
console.log('接收到子组件数据', msg)
}
render() {
return (
子组件:
)
}
}
子组件接收函数并且调用
class Child extends React.Component {
state = { childMsg: 'React' }
handleClick = () => {
this.props.getMsg(this.state.childMsg)
}
return (
)
}
注意:回调函数中 this 指向问题!
你要改色的文字
在 react 里面并没有兄弟组件通讯
但是如果在做项目的时候涉及到了兄弟组件通讯,那么就提供了一个核心的思想--状态提升
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
公共父组件职责:
提供共享状态
提供操作共享状态的方法
要通讯的子组件只需通过 props 接收状态或操作状态的方法
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
import Jack from "./jack";
import Rose from "./rose"
class App extends Component {
// 1. 状态提升到父组件
state = {
msg: ''
}
render() {
return (
父组件
{
// 2. 把状态给子组件显示
}
)
}
changeMessage = (msg) => {
this.setState({
msg
})
}
}
// 现在的写法1
// // 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
// const element = (
//
//
//
// );
// // 参数1:渲染的 react 元素即虚拟 DOM
// // 参数2:需要渲染到哪个容器中
// const root = ReactDom.createRoot(document.getElementById('root'));
// root.render(element);
// 现在的写法2
ReactDom.createRoot(document.getElementById('root')).render( )
// 老版本写法
// ReactDom.render( , document.getElementById('root'))
jack.js
import React, { Component } from 'react'
export default class Jack extends Component {
render() {
return (
Jack子组件
)
}
say = () => {
this.props.say('you jump i look')
}
}
rose.js
import React, { Component } from 'react'
export default class Rose extends Component {
render() {
return (
Rose子组件--{this.props.msg}
)
}
}
思考:App 组件要传递数据给 Child 组件,该如何处理?
处理方式:使用 props 一层层组件往下传递(繁琐)
更好的方式:使用 Context
推荐方式:在后续可能就不用 context 了,而是用 redux
作用:跨组件传递数据(比如:主题、语言等)
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
import Father from "./Father"
// 1. 调用 React.createContext() 创建 Provider(提供数据)
// 和 Consumer(消费数据) 两个组件
// Provider 包裹根元素,Provider 就是最大的根元素
const { Provider, Consumer } = React.createContext()
export { Consumer }
class App extends Component {
state = {
color: 'green'
}
render() {
return (
APP组件
)
}
}
// 现在的写法1
// // 幽灵节点:节点不会渲染任何的内容,跟 vue 里面的 template 标签一样
// const element = (
//
//
//
// );
// // 参数1:渲染的 react 元素即虚拟 DOM
// // 参数2:需要渲染到哪个容器中
// const root = ReactDom.createRoot(document.getElementById('root'));
// root.render(element);
// 现在的写法2
ReactDom.createRoot(document.getElementById('root')).render( )
// 老版本写法
// ReactDom.render( , document.getElementById('root'))
Father.js
import React, { Component } from 'react'
import Son from './Son'
export default class Father extends Component {
render() {
return (
父组件
)
}
}
Son.js
import React, { Component } from 'react'
import Sun from "./Sun";
export default class Son extends Component {
render() {
return (
子组件
)
}
}
Sun.js
import React, { Component } from 'react'
import { Consumer } from './index'
export default class Sun extends Component {
render() {
return (
{
(value) => 子孙组件--{value}
}
)
}
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React from 'react';
import ReactDom from 'react-dom/client';
import { Component } from 'react';
import Father from "./Father"
// 1. 调用 React.createContext() 创建 Provider(提供数据)
// 和 Consumer(消费数据) 两个组件
// Provider 包裹根元素,Provider 就是最大的根元素
const { Provider, Consumer } = React.createContext()
export { Consumer }
class App extends Component {
state = {
color: 'red',
bgColor: 'skyblue'
}
changeColor = (color) => {
this.setState({
color
})
}
render() {
const { color, bgColor } = this.state
return (
APP组件
)
}
}
ReactDom.createRoot(document.getElementById('root')).render( )
Father.js
import React, { Component } from 'react'
import Son from './Son'
export default class Father extends Component {
render() {
return (
父组件
)
}
}
Son.js
import React, { Component } from 'react'
import Sun from "./Sun";
export default class Son extends Component {
render() {
return (
子组件
)
}
}
Sun.js
import React, { Component } from 'react'
import { Consumer } from './index'
export default class Sun extends Component {
render() {
return (
{
({color, bgColor, changeColor}) =>
子孙组件
}
)
}
}
总结:
如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
Context提供了两个组件:Provider 和 Consumer
Provider组件:用来提供数据
Consumer组件:用来消费数据
表示该组件的子节点,只要组件有子节点,props就有该属性
children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
index.js
function Hello(props) {
return (
该组件的子节点:{props.children}
)
}
我是子节点
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,可能会导致组件内部报错。组件的使用者不能很明确的知道错误的原因 校验接收的props的数据类型,增加组件的健壮性
对于某些应用程序来说,你可以使用 Flow 或 TypeScript等JavaScript扩展来对整个应用程序做类型检查。但即使你不使用这些扩展,React 也内置了一些类型检查的功能。要在组件的 props 上进行类型检查,你只需配置特定的 propTypes
属性:
props校验允许在创建组件的时候,就约定props的格式、类型等
作用:规定接收的props的类型必须为数组,如果不是数组就会报错,增加组件的健壮性。
通过静态属性设置propTypes
属性去校验外部数据的类型
propTypes
是固定的单词,不能写错了,写错了校验不生效。
如果是老版本,你需要手动下载prop-types
插件
yarn add prop-types
导入 prop-types 包
使用组件名.propTypes = {} 来给组件的props添加校验规则
校验规则通过 PropTypes 对象来指定
可以在类外部设置静态属性propTypes
import React, { Component } from 'react'
import PropTypes from 'prop-types';
class PropsClass extends Component {
constructor(props) {
super(props);
}
render() {
return (
标题:{this.props.title}
)
}
}
PropsClass.propTypes = {
title: PropTypes.string,
isShow: PropTypes.bool
}
export default PropsClass;
也可以在类内部设置static静态属性propTypes
import React, { Component } from 'react'
import PropTypes from 'prop-types';
class PropsClass extends Component {
static propTypes = {
title: PropTypes.string,
isShow: PropTypes.bool
}
constructor(props) {
super(props);
}
render() {
return (
标题:{this.props.title}
)
}
}
export default PropsClass;
import React from 'react'
import PropTypes from 'prop-types';
function Props(props) {
console.log(props);
return (
标题:{props.title}
姓名:{props.user.name}
年龄:{props.user.age}
地址:{props.user.address}
)
}
Props.propTypes = {
title: PropTypes.string.isRequired
}
export default Props;
常见类型: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 设置默认值,在未传入 props 时生效
通过静态属性defaultProps
设置外部数据的默认值
defaultProps
这个单词是固定的,不能写错,写错了默认值不生效。
1)在类里面通过static
设置静态属性
import React, { Component } from 'react'
class PropsClass extends Component {
static defaultProps = {
title: '80期'
}
constructor(props) {
super(props);
}
render() {
return (
标题:{this.props.title}
)
}
}
export default PropsClass;
2)可以在类外部设置静态属性
import React, { Component } from 'react'
class PropsClass extends Component {
constructor(props) {
super(props);
}
render() {
return (
标题:{this.props.title}
)
}
}
PropsClass.defaultProps = {
title: '80期111'
}
export default PropsClass;
以上两种方式是等价的,都是静态属性
外部数据props的默认值,需要在组件上添加静态属性defaultProps
defaultProps
这个单词是固定的,不能写错,写错了默认值不生效。
import React from 'react'
function Props(props) {
return (
标题:{props.title}
姓名:{props.user.name}
年龄:{props.user.age}
地址:{props.user.address}
)
}
Props.defaultProps = {
title: '蜗牛'
}
export default Props;