组件通讯
组件的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) }