Render Props
Render Props
在 React 中,代码复用的最基本单位就是组件,但是如果组件中也出现了重复的代码,那么该如何复用呢?
通过某种方式将代码中公共的部分抽取出来,这些公共的部分就被称之为横切关注点(Cross-Cutting Concerns)。
在 React 中,常见的有三种方式来进行横切关注点的抽离:
- 高阶组件(HOC)。
- Render Props。
- 自定义 Hook。
what
Render Props 并非什么新语法或特性,而是一种在 React 组件之间使用一个值为函数的 prop 进行代码共享的技术,也是 React 中一种组件的设计模式。
来看一个示例:
- App.jsx
- childCom1.jsx
- childCom2.jsx
import ChildCom1 from "./ChildCom1"
import ChildCom2 from "./ChildCom2"
function App() {
return (
<div style={{
display: 'flex',
justifyContent: 'space-between',
width: "850px"
}}>
<ChildCom1 />
<ChildCom2 />
</div>
);
}
export default App;
import { useState } from 'react';
function ChildCom1() {
const [points, setPoints] = useState({
x: 0,
y: 0
})
function handleMouseMove(e) {
setPoints({
x: e.clientX,
y: e.clientY
})
}
return (
<div style={{
width: '400px',
height: '400px',
backgroundColor: 'red'
}} onMouseMove={handleMouseMove}>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({points.x}, {points.y})</p>
</div>
);
}
export default ChildCom1
import { useState } from 'react';
function ChildCom2() {
const [points, setPoints] = useState({
x: 0,
y: 0
})
function handleMouseMove(e) {
setPoints({
x: e.clientX,
y: e.clientY
})
}
return (
<div style={{
width: '400px',
height: '400px',
backgroundColor: 'grey',
position: 'relative',
overflow: 'hidden'
}} onMouseMove={handleMouseMove}>
<h1>移动鼠标!</h1>
<div style={{
width: '15px',
height: '15px',
borderRadius: "50%",
backgroundColor: 'white',
position: 'absolute',
left: points.x - 5 - 460,
top: points.y - 5 - 10,
}}></div>
</div>
);
}
export default ChildCom2;
在上面的代码中,App 根组件下渲染了两个子组件,这两个子组件一个是显示鼠标的位置,另外一个是根据鼠标位置显示一个追随鼠标移动的小球。
仔细观察代码,会发现这两个子组件内部的逻辑基本上是一样的,只是最终渲染的内容不一样,此时就可以使用 Render Props 对横切关注点进行一个抽离。
使用 Render Props 的方式很简单:在一个组件中使用一个值为函数的 prop,函数的返回值为要渲染的视图。
- App.jsx
- childCom1.jsx
- childCom2.jsx
- MouseMove.jsx
import ChildCom1 from "./components/ChildCom1";
import ChildCom2 from "./components/ChildCom2";
import MouseMove from "./components/MouseMove";
function App() {
return (
<div style={{
display: 'flex',
justifyContent: 'space-between',
width: "850px"
}}>
<MouseMove render={props => <ChildCom1 {...props} />} />
<MouseMove render={props => <ChildCom2 {...props} />} />
</div>
);
}
export default App;
import { useState } from 'react';
function ChildCom1({ points, handleMouseMove}) {
return (
<div style={{
width: '400px',
height: '400px',
backgroundColor: 'red'
}} onMouseMove={handleMouseMove}>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({points.x}, {points.y})</p>
</div>
);
}
export default ChildCom1
import { useState } from 'react';
function ChildCom2({ points, handleMouseMove}) {
return (
<div style={{
width: '400px',
height: '400px',
backgroundColor: 'grey',
position: 'relative',
overflow: 'hidden'
}} onMouseMove={handleMouseMove}>
<h1>移动鼠标!</h1>
<div style={{
width: '15px',
height: '15px',
borderRadius: "50%",
backgroundColor: 'white',
position: 'absolute',
left: points.x - 5 - 460,
top: points.y - 5 - 10,
}}></div>
</div>
);
}
export default ChildCom2;
import { useState } from 'react';
function MouseMove(props) {
const [points, setPoints] = useState({
x: 0,
y: 0
})
function handleMouseMove(e) {
setPoints({
x: e.clientX,
y: e.clientY
})
}
return (
props.render ? props.render({ points, handleMouseMove }) : null
);
}
export default MouseMove;
如上,创建了一个 MouseMove 组件,该组件封装了 ChildCom1 和 ChildCom2 组件的公共逻辑。
该组件的 props 接收一个名为 render 的 prop,只不过该参数对应的值为一个函数,调用时将对应的状态和事件处理函数传递过去,该函数返回一段视图。
虽然这个方式的名字叫做 Render Props,但并不是说必须要提供一个名为 render 的 prop,只要封装公共逻辑的组件能够得到要渲染的视图即可。很多情况下,也将 children 作为 Render Props。
props.children ? props.children({ points, handleMouseMove }) : null
when
同样是抽离横切关注点,利用 HOC 也能达到相同的效果。
- App.jsx
- withMouseMove.js
import ChildCom1 from "./ChildCom1";
import ChildCom2 from "./ChildCom2";
import withMouseMove from "./withMouseMove"
const NewChildCom1 = withMouseMove(ChildCom1);
const NewChildCom2 = withMouseMove(ChildCom2);
function App() {
return (
<div style={{
display: 'flex',
justifyContent: 'space-between',
width: "850px"
}}>
<NewChildCom1 />
<NewChildCom2 />
</div>
);
}
export default App;
import { useState } from "react";
function withMouseMove(Com) {
// 返回一个新组件
return function NewCom() {
const [points, setPoints] = useState({
x: 0,
y: 0,
});
function handleMouseMove(e) {
setPoints({
x: e.clientX,
y: e.clientY,
});
}
const mouseHandle = { points, handleMouseMove };
return <Com {...mouseHandle} />;
};
}
export default withMouseMove;
那么什么时候使用 Render Props?什么时候使用 HOC?
一般来讲,Render Props 应用于组件之间功能逻辑完全相同,仅仅是渲染的视图不同,这时可以通过 Render Props 来指定要渲染的视图是什么。
而 HOC 一般是抽离部分公共逻辑,也就是说组件之间有一部分逻辑相同,但是各自也有自己独有的逻辑,那么这时使用 HOC 比较合适,可以在原有的组件的基础上做一个增强处理。