组件通讯
多个组件之间进行数据共享就叫做组件通讯。
组件通讯通过props和context来实现
props
基本使用
props是用于接受外部传递给组件的数据。
props是通过给标签添加属性的方式来床底数据。
在函数组件中通过props参数接受,类组件中通过this.props来接收,props是一个对象。
特点
- 可以传递任意类型的数据(基本类型、引用类型、jsx),默认会传递字符串,非字符串要用插值表达式包裹。
- props的属性是只读的
- 使用类组件时如果使用了构造器函数,应该把props传递给super(),否则无法获取到props
深入
children属性
- 在使用标签时如果标签内有子节点,props就是自动获得children属性,属性的内柔就是子节点的内容,
- children属性和props一样都可以传入任意数据类型(基本类型,引用类型,jsx),它是一个数组
props的校验
React允许在创建组件时指定props属性的类型和格式,可以在传入的数据格式错误时抛出错误信息。
- 使用步骤:
- 安装prop-types包
cnpm i props-types
- 导入对应的包
import PropTypes from 'prop-types'
- 使用组件名.PropTypes来添加校验规则
App.PropTypes = {colors: PropTypes.array}
- 常见类型:
array bool func number object string symbol
- 必填项
在设置属性后再添加一项isRequired属性设置为必填项
getArc: PropTypes.func.isRequired
- 指定特定结构的对象
option: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
})
默认值
组件名.defaultProps = {
pageSize: 10
}
通信类型
父传子
- 父组件提供数据
- 在子组件标签中添加属性来传值
- 子组件内部通过props来接收
子传父
- 父组件提供回调函数
- 将回调函数作为属性传递给子组件
- 子组件通过props接受回调函数
- 子组件定义函数作为父组件回调函数的参数调用
- 将要传递的数值作为子组件函数的参数传入
兄弟组件
- 将共享的状态提升到最近的公共父组件中,称为状态提升。
- 父组件提供共享的状态和操作状态的方法。
- 子组件通过props接收状态和操作方法(按照子传父和父传子的方式)
context
如果父子的嵌套层次很深,可以通过context跨层次传递。
- 使用React.createContext()来创建提供数据(Provider)和使用数据(Consumer)的两个组件。
const { Provider , Consumer } =
React.createContext();
- 使用Provider组件来包裹我们自定义的组件。
<Provider>
<div></div>
</Provider>
- 设置value属性设置要传递的数据。
<Provider value='pink'>
<div></div>
</Provider>
- 通过Consumer组件接受值
<Consumer>
(data) => <span>这里是数据:{data}</span>
</Consumer>
生命周期
只有类组件才有生命周期。
组件复用
组件的复用其实就是复用state和setState,有renderprops和高阶组件两种模式来实现复用。
render props模式
思路
思路:将state和操作state的方法封装到组件中。
添加一个函数作为props,用这个函数来将组件内部的值公开,通过这个函数的返回值来渲染UI
代码实现
复用
刚才是显示鼠标的坐标,这个组件还可以在鼠标位置显示图片充当特殊指针
这种方式可以实现逻辑的复用,但不能复用UI
不用修改组件,直接重写UI就可以
children属性
这样写可读性更强
最终优化
- 添加检验:类型和必选项
App.propTypes = {
children: PropTypes.func.isRequired
}
- 解除事件
// 卸载组件时解除事件
componentWillUnmount(){
window.removeEventListener('mousemove',this.changeMouse)
}
高阶组件模式
思路
高阶组件(HOC)实际上是一个函数,接收一个组件,返回一个增强后的组件。
函数内部提供一个类组件,这个类组件提供可以复用的逻辑代码,通过props将其传递给传入函数的组件。
使用步骤
- 创建with开头的函数。
- 指定函数参数(大写字母开头,作为要渲染的组件,这个组件提供Ui结构)。
- 函数内部创建类组件,提供可以复用的逻辑代码。
- 在类组件中渲染参数组件,同时将状态通过props传递给参数组件。
- 返回类组件,这样就有了一个同时包含UI和逻辑代码的组件。
// 高阶组件函数
function withMouse (Com) {
class App extends React.Component {
state = {
x: 0,
y: 0
}
// 鼠标事件
changeMouse = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 在生命周期中绑定事件
componentDidMount(){
window.addEventListener('mousemove',this.changeMouse)
}
// 卸载组件时解除事件
componentWillUnmount(){
window.removeEventListener('mousemove',this.changeMouse)
}
// render
render(){
return (<Com {...this.state}></Com>);
}
}
return App;
}
// 函数组件
const Mouse = props => (
<h1>
<div>x: {props.x}</div>
<div>y: {props.y}</div>
</h1>
)
// 调用高阶组件函数
const StrongMouse = withMouse(Mouse)
displayName
被高阶组件包装的组件在devtools中会显示原始的组件名(高阶组件函数return时的名称),不利于调试。
// 高阶组件函数
function withMouse (Com) {
class App extends React.Component {
state = {
x: 0,
y: 0
}
// 鼠标事件
changeMouse = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 在生命周期中绑定事件
componentDidMount(){
window.addEventListener('mousemove',this.changeMouse)
}
// 卸载组件时解除事件
componentWillUnmount(){
window.removeEventListener('mousemove',this.changeMouse)
}
// render
render(){
return (<Com {...this.state}></Com>);
}
}
// 设置displayName
App.displayName = `WithApp${getDiaplayName(Com)}`
return App;
}
// 设置displayName的辅助函数,通用
function getDisplayName (Com) {
return Com.displayName || Com.Name || 'Component'
}
props传递
上面的代码中,在高阶组件函数的返回值上直接添加属性是加不上去的,因为这个时候props已经丢失了
需要在渲染组件时,将state和props一起传递下去
// 高阶组件函数,最终版本,新增加了props传递
function withMouse (Com) {
class App extends React.Component {
state = {
x: 0,
y: 0
}
// 鼠标事件
changeMouse = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 在生命周期中绑定事件
componentDidMount(){
window.addEventListener('mousemove',this.changeMouse)
}
// 卸载组件时解除事件
componentWillUnmount(){
window.removeEventListener('mousemove',this.changeMouse)
}
// render
render(){
return (<Com {...this.state} {...this.props}></Com>);
}
}