1.render-props
1.React组件复用概述
- 思考:如果两个组件中的部分功能相似或相同,该如何处理?
- 处理方式:复用相似的功能(联想函数封装)
- 复用什么?1. state 2. 操作state的方法 (组件状态逻辑 )
- 两种方式:1. render props模式 2. 高阶组件(HOC)
- 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
2.render props 模式
- 思路:将要复用的state和操作state的方法封装到一个组件中
- 问题1:如何拿到该组件中复用的state?
- 在使用组件时,添加一个值为函数的prop,通过 函数参数 来获取(需要组件内部实现)
<Mouse render={(mouse) => {}}/>
- 问题2:如何渲染任意的UI?
- 使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
<Mouse render={(mouse) => ( <p>鼠标当前位置 {mouse.x},{mouse.y}</p> )}/>
3.render props模式的使用步骤
-
创建Mouse组件,在组件中提供复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
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(){ //这一步先写null,后面会完善 return null } }
-
将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
render() { return this.props.render(this.state) } <Mouse render={(mouse) => {return ()}} />
-
使用 props.render() 的返回值作为要渲染的内容
render={(mouse) => { return ( <p> 鼠标位置:{mouse.x},{mouse.y} </p> )
只实现了状态结构的复用,并没有实现UI结构的复用
-
在鼠标上加上图片,跟随鼠标一起移动
-
导入图片资源
import img from './images/star.png'
-
将图片写下以下内容
{/* 图片 */} <Mouse render={mouse => { return <img src={img} alt="star" style={{ position: 'absolute', //减64是因为图片128*128,想要鼠标在图片中心,则需要-64 top: mouse.y - 64, left: mouse.x - 64 }} /> }}></Mouse>
4.children代替render属性
- 注意:并不是该模式叫 render props 就必须使用名为render的prop,实际上可以使用任意名称的prop
- 把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式
- 推荐:使用 children 代替 render 属性
//组件内部
render() {
return this.props.children(this.state)
}
<Mouse>
{mouse => {
return (
<p>
鼠标位置:{mouse.x},{mouse.y}
</p>
)
}}
</Mouse>
<Mouse >{mouse =>
(<img src={img} alt="star" style={{
position: 'absolute',
top: mouse.y - 64,
left: mouse.x - 64
}}
/>)
}
</Mouse>
Context 中的用法:
<Consumer>
{data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
代码优化
-
推荐:给 render props 模式添加 props校验
import propTypes from 'prop-types' // 添加props校验 Mouse.propTypes = { children:propTypes.func.isRequired }
-
应该在组件卸载时解除 mousemove 事件绑定
因为componentDidMount是我们自己手动开的,既然开了一个,卸载时,就要关这一个
// 推荐:在组件卸载时移除事件绑定 componentWillUnmount(){ window.addEventListener('mousemove',this.handleMouseMove) }
2.高阶组件
1.概述
- 目的:实现状态逻辑复用
- 采用 包装(装饰)模式 ,比如说:手机壳
- 手机:获取保护功能
- 手机壳 :提供保护功能
- 高阶组件就相当于手机壳,通过包装组件,增强组件功能
2.思路分析
- 高阶组件(HOC,Higher-Order Component)是一个函数,接收要包装的组件,返回增强后的组件
- 高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,
class Mouse extends React.Component { render() { return <WrappedComponent {...this.state} /> } }
- 通过prop将复用的状态传递给被包装组件 WrappedComponent
const EnhancedComponent = withHOC(WrappedComponent)
3.高阶组件使用步骤
-
创建一个函数,名称约定以 with 开头
function withMouse() {}
-
指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
function withMouse(WrappedComponent) {}
-
在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
function withMouse(WrappedComponent) { class Mouse extends React.Component {。。。} return Mouse }
-
在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
UI组件由WrappedComponent提供
// Mouse组件的render方法中: //Mouse组件的状态->...this.state return <WrappedComponent {...this.state} />
-
调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
//Positon组件作为参数传进来 // 创建组件 const MousePosition = withMouse(Position) // 渲染组件 <MousePosition />
-
导入图片
import img from './images/star.png'
-
图片的组件
const Star = props =>( <img src={img} alt="" style={{ position:"absolute", top:props.y -64, left:props.x -64 }} /> )
-
让图片跟随鼠标移动
//调用高阶组件来增强图片的组件 const MouseStar = withMouse(Star) {/* 渲染增强后的组件 */} <MouseStar></MouseStar>
-
完整代码
// 创建高阶组件 function withMouse(WrappedComponent) { // 该组件提供复用的状态逻辑 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) } //在组件卸载时移除事件绑定 componentWillUnmount() { window.addEventListener('mousemove', this.handleMouseMove) } render() { return <WrappedComponent {...this.state}></WrappedComponent> } } return Mouse } // 用来测试高阶组件 const Position = props => ( <p> 鼠标当前位置:(x:{props.x},y:{props.y}) </p> ) // 获取增强后的组件 const MousePosition = withMouse(Position) class App extends React.Component { render() { return ( <div> <h1>高阶组件</h1> {/* 渲染增强后的组件 */} <MousePosition></MousePosition> </div> ) } }
4.设置displayName
- 使用高阶组件存在的问题:得到的两个组件名称相同
- 原因:默认情况下,React使用组件名称作为 displayName
- 解决方式:为 高阶组件 设置 displayName 便于调试时区分不同的组件
- displayName的作用:用于设置调试信息(React Developer Tools信息)
- 设置方式:
//放在创建高阶组件内部
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
设置 getDisplayName()
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
由上图变成下图:
5.传递props
- 问题:props丢失
- 原因:高阶组件没有往下传递props
- 解决方式:渲染 WrappedComponent 时,将 state 和 this.props 一起传递给组件
- 传递方式:
<WrappedComponent {...this.state} {...this.props} />
总结
- 组件通讯是构建 React 应用必不可少的一环。
- props 的灵活性让组件更加强大。
- 状态提升是React组件的常用模式。
- 组件生命周期有助于理解组件的运行过程。
- 钩子函数让开发者可以在特定的时机执行某些功能。
- render props模式和高阶组件都可以实现组件状态逻辑复用。
- 组件极简模型: (state, props) => UI