组件的复用
组件复用
△组件复用就是将组件中相同的业务逻辑抽取出来进行封装
△复用两种东西:
- state
- 操作 state 的方法
△实现复用有两种方式:
-
render props 模式
-
高阶组件(HOC)
△组件复用没有新的API,是由 React 自身特点(编码技巧)演化而来的固定模式
render props模式
1.创建鼠标位置组件
核心思想:子组件向父组件传值,父组件渲染页面时,使用子组件提供的state值
实现思路:
- 将要操作的状态(state) 和 操作状态的方法封装到子组件中
- render 函数中不再渲染页面,而是返回一个可供父组件调用的方法,并将state作为参数传入
- 父组件在调用子组件时可以传入函数,函数的形参能够接收到子组件传递的state
实现步骤:
- 创建子组件 Mouse,在该组建中提供状态和修改状态的代码
- Mouse 的 render 函数不再渲染页面,而是通过props向外界提供一个函数,并将state作为参数传入
- 在App组件中渲染Mouse,并在Mouse中使用属性调用函数
Mouse组件 (component/mouse.js):
import React from 'react'
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
moveMouse = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount () {
window.addEventListener('mousemove', this.moveMouse)
}
render () {
// mouse组件不再渲染页面,而是向外提供一个叫做render的方法
// 并且将 state 作为参数传入
return this.props.render(this.state)
}
}
export default Mouse
2.复用鼠标位置组件
实现思路: 在App组件(父组件)中调用 Mouse组件时,使用 render 方法得到Mouse组件(子组件)中提供的state,再进行页面渲染工作
App组件:
class App extends React.Component {
render () {
return (
<div>
<Mouse render={
mouse => {
return <div>鼠标位置: {
mouse.x} - {
mouse.y}</div>
}
}/>
<Mouse render={
mouse => {
return <img src={
img} alt="飞翔的大猪" style={
{
left: mouse.x - 50, top: mouse.y - 50}} />
}} />
</div>
)
}
}
3.children代替render props
return this.props.render(this.state)
中的 render 并不是固定的名称,使用什么都行- render props 核心思想还是利用子向父传值时候的回调函数。父组件向子组件传递一个函数,自组件执行该函数时将子组件中的state作为实参传入。
- children 是一个更好的选择,因为组件如果写成双标签形式,标签内部会被自动认为是 children
实现方案:
- mouse组件中,将返回值的 this.props.render 改为 this.props.children
- 调用Mouse组件时,将函数执行放在Mouse组件中
mouse-1.js:
import React from 'react'
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
moveMouse = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount () {
window.addEventListener('mousemove', this.moveMouse)
}
render () {
// 使用 children 替换 render
return this.props.children(this.state)
}
}
export default Mouse
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import Mouse from './components/mouse-1'
import img from './assets/images/pig.jpg'
import './assets/css/pig.css'
class App extends React.PureComponent {
// 鼠标位置
mousePosition = data => {
return (
<div>鼠标位置: {
data.x} - {
data.y}</div>
)
}
// 飞猪
pig = data => {
return (
<img src={
img} alt="飞猪" style={
{
left: data.x, top: data.y}} />
)
}
render () {
return (
<div>
{
/* 调用组件时将函数写在组件标签之间 */}
<Mouse>
{
this.mousePosition }
</Mouse>
<Mouse>
{
this.pig }
</Mouse>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
高阶组件
-
高阶组件(HOC,Higher-Order-Component) 采用 包装模式 实现组件的复用
-
高阶组件就是一个函数,接收要包装组件,返回一个增强功能的组件
1.基本使用
核心思想: 父组件向子组件传值,父组件提供state,并在调用子组件时将state传递给子组件;子组件使用porps接收state的值,进行页面渲染
实现步骤:
- 创建增强组件函数
△ 创建一个函数,名称必须 以 with 开头
△ 指定函数参数,参数名也要以大写字母开头 (实际上是组件名称)
△ 在函数内部创建一个类组件,最后要返回该类组件 - 定义子组件,子组件使用 props 接收父组件的state,并进行渲染
- 在 App 组件中调用增强组件
代码实现:
第一步:创建增强组件函数:
// 创建一个以 with 开头的函数
function withMouse (WarppedComponent) {
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount () {
window.addEventListener('mousemove', this.handleMouseMove)
}
// render 方法中渲染子组件
render () {
return <WarppedComponent {
...this.state}></WarppedComponent>
return Mouse
}
第二步:定义子组件,子组件使用 props 接收父组件的state,并进行渲染:
class Position extends React.PureComponent {
render () {
return (
// 使用 props 接收父组件传过来的 state,再进行调用
<div>鼠标位置: {
this.props.x} - {
this.props.y}</div>
)
}
}
const PositionMouse = withMouse(Position)
第三步:在 App 组件中调用增强组件:
class App extends React.PureComponent {
render () {
return (
<div>
<PositionMouse />
</div>
)
}
}
2.displayName
- 同一个高阶组件使用两次时,在页面中会展示出两个相同的组件名,这样会造成调试不便的情况
- 默认情况下,高阶组件会以 displayName 属性中的值作为标签名称
- 在没有 displayName 的情况下使用 name 属性中的值作为标签名称
打印 Mouse 对象
function withMouse (WrappedComponent) {
class Mouse extends React.PureComponent {
state = {
... }
handleMouseMove = e => {
... }
componentDidMount () {
... }
render () {
... }
}
console.log(Mouse)
return Mouse
}
打印 Mouse 类能看见 name 属性,但是看不见 displayName 属性,所以标签名就使用 name 对应的值,
但是我们手动设置 displayName 后,它的优先级是高于 name 属性的
function withMouse (WrappedComponent) {
class Mouse extends React.PureComponent {
state = {
... }
handleMouseMove = e => {
... }
componentDidMount () {
... }
render () {
... }
}
// 为 Mouse 增加 displayName 属性
Mouse.displayName = 'MyCom'
// 打印 Mouse 对象
console.log(Mouse)
return Mouse
}
Mouse 现在的结构:
页面上的元素:
让高阶组件名称差异化的解决方案: 动态设置组件的 displayName
(如果组件本身有 displayName 就使用 displayName,如果没有就使用组件的 name)
3.传递props
渲染高阶组件时,如果给组件传递属性和值会丢失,因为高阶组件没有向下继续再传递值
解决方案: 渲染 WarppedComponent 时,将 state 和 props 一起传递给组件
class App extends React.PureComponent {
render () {
return (
<div>
{
/* 渲染高阶组件时,向组件中传入数据 */}
<PositionMouse a={
123} />
</div>
)
}
}
function withMouse (WarppedComponent) {
class Mouse extends React.PureComponent {
state = {
...}
handleMouseMove = e => {
...}
componentDidMount () {
...}
render () {
return <WarppedComponent {
...this.state} {
...this.props}></WarppedComponent>
}
}
Mouse.displayName = `withMouse${
getDisplayName(WarppedComponent)}`
return Mouse
}
class Position extends React.PureComponent {
render () {
console.log(this.props)
return (
<div>鼠标位置: {
this.props.x} - {
this.props.y}</div>
)
}
}
注意:
- render 中调用子组件,同时将 state 和 props 都传入 WarppedComponent
- Position 组件中的 props 即接收了Mouse组件传递的 state 又接收了props,所以在this.props中保存了