- Render Props
- HOC 高阶组件
- React hooks
1. Render Props
render props 指的是某个值为函数的 props 值,在 React 组件之间共享代码的技术。说白了就是某个 prop 为函数形式,为了代码复用。
1
| <DataProvider render={data => <div>hello {data.name}</div>} />
|
实例
下面是一个跟踪鼠标位置的组件:
live demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class MouseTracker extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; }
handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); }
render() { const { x, y } = this.state; return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> <h1>Move the mouse around!</h1> <p> The current mouse position is ({x}, {y}) </p> </div> ); } }
|
现在的问题是我们如何复用这个逻辑。如果其他组件也需要鼠标位置信息,怎么复用?
假设我们有一个 Cat 组件需要鼠标位置信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; }
handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> <Cat mouse={this.state} /> </div> ); } }
|
这种写法虽然满足了需求,但是却没有达到复用的效果。接下来我们看看利用 render props 如何进行重构:
live demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; }
handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); }
render() { const { render } = this.props; return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> {render(this.state)} </div> ); } }
class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse render={mouse => <Cat mouse={mouse} />} /> </div> ); } }
|
可以看到,Cat 和 Fish 都很好的复用了 Mouse 组件的逻辑。
这个 render 参数也可以是其他名字,甚至是 children 参数
1 2 3 4 5 6 7 8 9 10
| class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse>{mouse => <Cat mouse={mouse} />}</Mouse> </div> ); } }
|
Render Props 的模式需要注意一点,例如上边的例子,children 参数是一个匿名函数,所以每次渲染都会创建一个不一样的,导致像 PureComponent 这样的优化手段失效。为了解决这个问题,可以将 children 函数变成一个类方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MouseTracker extends React.Component { renderMouse(mouse) { return <Cat mouse={mouse} />; } render() { return ( <div> <h1>Move the mouse around!</h1> <Mouse>{this.renderMouse}</Mouse> </div> ); } }
|
2. HOC 高阶组件
高阶组件(HOC)是 React 中用于组件逻辑复用的模式。其实如果理解了 Render Props 模式,就很容易理解 HOC 模式。拿第一版的 Mouse 举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class CatWithMouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; }
handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> <Cat mouse={this.state} /> </div> ); } }
class FishWithMouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; }
handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> <Fish mouse={this.state} /> </div> ); } }
|
可以看到 CatWithMouse 和 FishWithMouse 除了 render 中的一行代码不同外,其他都是重复的。
所以首先就会想到这部分提取成参数岂不是很好。
提取不同成分到 props 中
如果放到 props 里,那参数最好是个函数形式,如果props.children
不是函数形式,那就访问不到this.state
了。你看,一步步演变成了 render props 的形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class FishWithMouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; }
handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> {this.props.children(this.state)} </div> ); } }
|
提取不同成分到高阶函数中
首先先看一个例子
1 2 3 4 5
| functon high(selfFunc){ return function inner(...args){ selfFunc(...args) } }
|
这个例子里,我们把selfFunc
提取到高阶函数中去,然后生成一个新的函数体。
然后我们回到上边的问题中,我们把不同部分也提取到高阶函数的参数中来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function withMouse(Component) { return class FishWithMouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; }
handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); }
render() { return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> <Component {...props} mouse={this.state} /> </div> ); } }; }
const CatWithMouse = withMouse(Cat);
|
这就是高阶组件了,其实很简单:把代码中多变的部分提取成参数,然后返回一个新的 class 即可
读到这里,有没有发现 Render Props
和 HOC
是很容易互相转换的。
3. React hooks
React hooks 相关内容请移步: