this.props.children
<App>
此处的内容,就是组件的 children,将来通过组件的 props.children 就可以获取到这些子节点了
App>
作用:规定组件props的类型约束, 减少开发中的错误
prop-types 校验规则
安装 : yarn add prop-types
导入 : import Propypes from 'prop-types'
给组件添加校验规则
// 要对某个组件里的某个属性进行校验
Child.propTypes = {
// age 表示要校验的属性名称
// number 表示类型是数字
// isRequied 表示必填项
age : PropTypes.number.isRequired,
name : PropTypes.string,
arr : PropTypes.array,
fn : PropTypes.func,
isOK : PropTypes.bool,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 你可以让你的 prop 只能是特定的值,指定它为 枚举类型。
num : PropTypes.oneOf(['News', 'Photos']).isRequired,
// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
}
组件.defaultProps = {}
这样的方式来给组件添加默认值// 默认值
Child.defaultProps = {
age : 10
}
// 类组件
class Child extends React.Component {
//1.1. 构造函数
// 初始化数据
constructor() {
super()
this.state = {
name :'zs'
}
console.warn('constructor');
}
//1.2. 渲染UI
render () {
console.warn('render');
return (
<div>
<p>哈哈 {this.state.name}</p>
</div>
)
}
//1.3. 挂载之后 渲染之后
// 操作DOM ,发送ajax
componentDidMount () {
console.warn('componentDidMount');
}
}
componentDidUpdate (preProps,preState) {
之前的props 和 state
class Parent extends React.Component {
state = {
pmsg :'撩妹'
}
render () {
return <div>
<button onClick={this.updateProps2Child}>按钮修改props</button>
<Child msg={ this.state.pmsg }></Child>
</div>
}
updateProps2Child = () => {
// 演示2 : 修改父的state 其实就是修改 child 的props , 重新调用 child 的render
this.setState({
pmsg : '撩汉'
})
}
}
//2. 类组件
class Child extends React.Component {
state = {
name :'zs'
}
//2.1. 渲染UI
render () {
console.warn('render');
return (
<div>
<p>哈哈 {this.state.name}</p>
<p>父传过来的 : { this.props.msg }</p>
<button onClick={this.updateName}>按钮</button>
</div>
)
}
//更新state 的name 数据
updateName = () => {
// 演示1 : setState 重新调用 render
this.setState({
name : 'ls'
})
// 演示3 : forceUpdate 重新调用 render
this.forceUpdate()
}
// 2.2 组件更新
// 上一次的 props 和 上一次的state
componentDidUpdate (preProps,preState) {
console.warn('组件更新之前的数据', preState);
// this.state 当前最新的state
console.warn('组件更新之后的数据',this.state);
// 有条件的渲染 不然会造成死循环
if(this.state.name !== preState.name) {
this.setState({
name : this.state.name
})
}
}
}
componentWillUnmount
:在组件卸载时会触发,也就是 组件从页面中消失的时候// 第一步 : 在父组件里 点击按钮, 把 子组件给销毁
class Parent extends React.Component {
...
render () {
return <div>
<button onClick={this.changeShow}>按钮修改props</button>
{ this.state.isShow && (<Child msg={ this.state.pmsg }></Child>) }
</div>
}
changeShow = () => {
// 点击按钮 子组件被销毁卸载
this.setState({
isShow : false
})
}
}
// 第二步 : 在 Child 组件里
componentDidMount () {
//1. 开始定时器
this.timerId = setInterval(() => {
console.log('好嗨哟');
}, 1000);
//2. 给window注册鼠标触摸事件
window.addEventListener('mousemove', this.handleMouseMove)
}
// 触摸事件处理函数
handleMouseMove = (e) => {
console.log(e.clientX);
}
// 第三步 : 在将要卸载的钩子函数里 清除定时器和 移除鼠标移动事件
// 将要卸载
componentWillUnmount () {
//1. 清除定时器
clearInterval(this.timerId)
//2. 移除鼠标触摸事件
window.removeEventListener('mousemove',this.handleMouseMove)
}
什么情况下 会使用这两种模式 ? 复用
当两个组件或者多个组件有一部分state
和 操作 state 的方法
相同或者相似的时候, 就可以将这些代码逻辑使用这两种模式来实现复用
目的 : 状态逻辑复用 ==> 通俗点说 : 另一种封装形式
要复用的内容为 :
注意
状态逻辑
封装在一个组件中学习注意 :
// 位置
// 1. 使用 mouse组件 添加一个render属性, 值类型为一个函数
// 2. 可以得到一个mouse 位置坐标
// 3. 通过return 什么, 页面就会显示什么
ReactDOM.render(<Mouse
render={(mouse) => {
console.log(mouse);
return <h1>{mouse.x} - {mouse.y}</h1>
}}/>,document.getElementById('root'))
// 引入图片
import cat from './images/cat.png'
// 渲染
ReactDOM.render(<Mouse
render={mouse => {
return <img style={{ position : "absolute", left:mouse.x-64, top:mouse.y-64 }} src={cat} alt=''/>
} }/>,document.getElementById('root'))
render 属性
( render属性的值 是一个函数 )####分析Mouse 组件内容
// 封装 Mouse 组件 Mouse.js,
// 实现鼠标位置的复用
// 状态逻辑复用:1 state 2 操作状态
class Mouse extends React.Component {
// 提供鼠标位置的状态
state = {
x: 0,
y: 0
}
// 监听鼠标位置
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
// 组件卸载时执行清理工作:解绑事件
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
// 更新鼠标位置
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
render() {
// 通过 props 来获取到传递给组件的属性 render
// 因为 render 是一个函数,所以,就可以调用
// 在调用 render 函数时,将组件内部的状态,传递给 render 函数
// 最终,通过 render 的形参,就可以在组件外部获取到组件内部的状态了
// this.props.render(this.state)
// return null
// 通过 render 函数的返回值来指定要渲染的内容
// 所以,在组件内部直接使用 render 函数的返回值来作为该组件要渲染的内容
return this.props.render(this.state)
}
}
// 1 . 位置
ReactDOM.render(<Mouse>
{(mouse) => {
return <p>{mouse.x} - { mouse.y }</p> # +
} }
</Mouse>, document.getElementById('root'))
// 2 . 猫
ReactDOM.render(<Mouse>
{mouse => {
return <img style={{ position:'absolute', left:mouse.x-64, top:mouse.y-64 }} src={cat} alt=""/> # +
} }
</Mouse>, document.getElementById('root'))
// 3. Mouse.js 内部
render() {
// 改为 children
return this.props.children(this.state)
}
// 提供数据
<Provider value={ this.color } ></Provider>
// 消费数据/使用数据
<Consumer>
{ data => <span>--{data} </span>}
</Consumer>
yarn add react-spring
Render-props api ==> spring
// 引入
import {Spring} from 'react-spring/renderprops'
// 使用
ReactDOM.render(<Spring
config={{ duration:4000 }}
from={{ opacity: 0 }}
to={{ opacity: 1 }}>
{props => {
console.log(props);
return <div style={props}>hello</div>
}}
</Spring>, document.getElementById('root'))
高阶组件 : HOC : High-Order Component
实际上就是一个函数, 这个函数能够接受一个参数组件, 然后,返回一个增强后的组件
参数组件 : 就是需要被包装的组件
返回的组件 : 增强后的组件, 这个组件中就是通过Props来接收到复用的状态逻辑的
思想 : 就是组件在增强的过程中, 传入了一些数据给 组件的 props
const 增强后的组件 = 高阶组件(被包装组件)
// 这就是一个高阶组件
// 职责 : 1 提供鼠标位置状态 2 提供鼠标位置的方法
const withMouse = WrappedComponent => {
class Mouse extends React.Component {
// 鼠标位置状态
state = {
x: 0,
y: 0
}
// 进入页面时,就绑定事件
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
// 鼠标移动的事件处理程序
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 移除事件
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
return <WrappedComponent {...this.state} />
// return
}
}
return Mouse
}
//1. 演示1 位置组件
const Position = props => {
console.log(props) // 增强之前props是没有值的
return <p>x:{props.x} y:{props.y}</p>
}
// 如何使用?
// 增强后 = withMouse(增强前)
HOC_Position = withMouse(Position)
// 渲染
ReactDOM.render(<HOC_Position/>, document.getElementById('root'))
//2.演示2 : 移动猫
const Cat = props => {
console.log(props);
return <img style={{ position:"absolute", left:props.x-64, top:props.y-64 }} src={cat} alt=""/>
}
// 使用高阶组件增强一下
HOC_Cat = withMouse(Cat)
// 渲染
ReactDOM.render(<HOC_Cat/>, document.getElementById('root'))
const withMouse = (WrappedComponent) => {
class Mouse extends React.Component {
... 省略鼠标位置状态 和 操作鼠标位置的方法逻辑
render() {
return <WrappedComponent {...this.state} /> # 核心
}
}
return Mouse
}
// 同时渲染多个高阶组件的时候
ReactDOM.render(<div>
<HocPosition/>
<HocCat/>
</div>, document.getElementById('root'))
// 在 react-dev-tools 上面显示的是这样的
<Mouse>...</Mouse>
<Mouse>...</Mouse>
// 这样很不容易区分,所有需要添加displayName
const withMouse = (WrappedComponent) => {
class Mouse extends React.Component {
... 省略鼠标位置状态 和 操作鼠标位置的方法逻辑
}
// 给高阶组件设置名称,将来在 react-dev-tools 工具中,能够区分到底是哪一个高阶组件包装的组件
function getDisplayName(WrappedComponent) { # +
return WrappedComponent.displayName || WrappedComponent.name
}
Mouse.displayName = getDisplayName(WrappedComponent) # +
// 如果还想体现出来是高阶组价,就加个前缀
Mouse.displayName = `WithMouse_${getDisplayName(WrappedComponent)}` # +
return Mouse
}
- 补充:
- 先获取被包装组件的 displayName ,如果没有就获取它的名字,如果再没有来个默认的最起码不会报错或者返回undefined
- WrappedComponent.displayName || WrappedComponent.name
// 如果多加了属性
<HocPosition name='jack'/>
// 高阶组价内部 :
const withMouse = (WrappedComponent) => {
class Mouse extends React.Component {
... 省略鼠标位置状态 和 操作鼠标位置的方法逻辑
render() {
// 之前这里只传递给包装组件 state ,并没有传递props
return <WrappedComponent {...this.state} {...this.props} /> # ++++
}
}
return Mouse
}
// 使用
const Position = props => {
// 通过 props 就可以获取到传递给高阶组件的属性了
// ... 省略其他代码
}
state = {
count: 0
}
console.log('前 :',this.state.count) // 0
this.setState({
count: this.state.count + 1
})
// for (var i = 0; i < 1000; i++) {
// this.setState({
// count: i
// })
// }
console.log('后 :',this.state.count) // 0
格式 : setState( 对象, 回调 )
[官] : callback 它将在 setState
完成合并并重新渲染组件后执行。
通常,我们建议使用 componentDidUpdate()
来代替此方式。
this.setState({
count : this,state.count + 1
},() => {
console.log('这个回调函数会在状态更新后立即执行',this.state.count)
})
console.log('前', this.state.count) // 0
// 异步更新
this.setState({
count: this.state.count + 1 // 以为是 1
})
// 异步更新 + 获取 结果
this.setState(
{
count: this.state.count + 1 // 以为是 2
},
() => {
console.log(this.state.count) // 以为是 2 但是结果是1
}
)
// 两次更新 发现结果还都是 1 ,
# [官] 这种形式的 setState() 也是异步的,并且在同一周期内会对多个 setState 进行批处理。
# [官] 后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次。
let newObj = Object.assign({}, obj, { age: 20 }, { age: 30 })
# [官] 如果后续状态取决于当前状态,我们建议使用 updater 函数的形式代替:
// 异步更新
// 这个为什么就可以了, 因为是通过参数获取的, 是 React 控制 的,,返回的就是上次更新的
this.setState((state, props) => {
return {
count: state.count + 1 // 拿到最新的 0 + 1 = 1
}
})
// 异步更新 + 获取 结果
// 这个为什么就可以了, 因为是通过参数获取的, 是 React 控制 的,,返回的就是上次更新的
this.setState(
(state, props) => {
return {
count: state.count + 1 //拿到最新的1 + 1 = 2
}
},
() => {
console.log(this.state.count) // 结果是2
}
)
// 也可以简写
this.setState((state) => ({
count: state.count + 1 // 以为是 1
})
)
// 第一种格式 : setState(stateChnage, [callback]) ★
setSrare(对象, 回调)
// 第二种格式 :setState(updater, [callback]) ★
setSrare(函数式, 回调)
// 最常用的还是 第一种格式的简化操作 setState(stateChange) ★★★
this.setState({
count : this.state.count + 1
})
{
return {
count: state.count + 1 // 拿到最新的 0 + 1 = 1
}
})
// 异步更新 + 获取 结果
// 这个为什么就可以了, 因为是通过参数获取的, 是 React 控制 的,返回的就是上次更新的
this.setState(
(state, props) => {
return {
count: state.count + 1 //拿到最新的1 + 1 = 2
}
},
() => {
console.log(this.state.count) // 结果是2
}
)
- 简写
```js
// 也可以简写
this.setState((state) => ({
count: state.count + 1 // 以为是 1
})
)
// 第一种格式 : setState(stateChnage, [callback]) ★
setSrare(对象, 回调)
// 第二种格式 :setState(updater, [callback]) ★
setSrare(函数式, 回调)
// 最常用的还是 第一种格式的简化操作 setState(stateChange) ★★★
this.setState({
count : this.state.count + 1
})