- 原文地址:Use a Render Prop!
- 原文做者:Michael Jackson
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:yoyoyohamapi
- 校对者:MechanicianW Usey95
更新:我提交了一个 PR 到 React 官方文档,为其添加了 Render props。html
更新2:添加一部份内容来讲明 “children 做为一个函数” 也是相同的概念,只是 prop 名称不一样罢了。前端
几个月前,我发了一个 twitter:react
译注:@reactjs 我能够在一个普通组件上使用一个 render prop 来完成 HOC(高阶组件) 可以作到的事情。不服来辩。android
我认为,高阶组件模式 做为一个在许多基于 React 的代码中流行的代码复用手段,是能够被一个具备 “render prop” 的普通组件 100% 地替代的。“不服来辩” 一词是我对 React 社区朋友们的友好 “嘲讽”,随之而来的是一个系列好的讨论,但最终,我对我本身没法用 140 字来完整描述我想说的而感到失望。 我 决定在将来的某个时间点写一篇更长的文章 来公平公正的探讨这个主题。ios
两周前,当 Tyler 邀请我到 Phoenix ReactJS 演讲时,我认为是时候去对此进行更进一步的探讨了。那周我已经到达 Phoenix 去启动 咱们的 React 基础和进阶补习课 了,并且我还从个人商业伙伴 Ryan 听到了关于大会的好消息,他在四月份作了演讲。git
在大会上,个人演讲彷佛有点标题党的嫌疑:不要再写另外一个 HOC 了。你能够在 Phoenix ReactJS 的 YouTube 官方频道 上观看个人演讲,也能够经过下面这个内嵌的视频进行观看:github
若是你不想看视频的话,能够阅读后文对于演讲主要内容的介绍。可是严肃地说:视频要有趣多了 😀。typescript
若是你直接跳过视频开始阅读,但并无领会我所说的意思,就折回去看视频吧。演讲时的细节会更丰富。后端
个人演讲始于高阶组件主要解决的问题:代码复用。api
让咱们回到 2015 年使用 React.createClass
那会儿。假定你如今有一个简单的 React 应用须要跟踪并在页面上实时显示鼠标位置。你可能会构建一个下面这样的例子:
import React from 'react'
import ReactDOM from 'react-dom'
const App = React.createClass({
getInitialState() {
return { 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>The mouse position is ({x}, {y})</h1> </div>
)
}
})
ReactDOM.render(<App/>, document.getElementById('app'))
复制代码
如今,假定咱们在另外一个组件中也须要跟踪鼠标位置。咱们能够重用 <App>
中的代码吗?
在 createClass
这个范式中,代码重用问题是经过被称为 “mixins” 的技术解决的。咱们建立一个 MouseMixin
,让任何人都能经过它来追踪鼠标位置。
import React from 'react'
import ReactDOM from 'react-dom'
// mixin 中含有了你须要在任何应用中追踪鼠标位置的样板代码。
// 咱们能够将样板代码放入到一个 mixin 中,这样其余组件就能共享这些代码
const MouseMixin = {
getInitialState() {
return { x: 0, y: 0 }
},
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
})
}
}
const App = React.createClass({
// 使用 mixin!
mixins: [ MouseMixin ],
render() {
const { x, y } = this.state
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> <h1>The mouse position is ({x}, {y})</h1> </div>
)
}
})
ReactDOM.render(<App/>, document.getElementById('app'))
复制代码
问题解决了,对吧?如今,任何人都能轻松地将 MouseMixin
混入他们的组件中,并经过 this.state
属性得到鼠标的 x
和 y
坐标。
去年,随着ES6 class 的到来,React 团队最终决定使用 ES6 class 来代替 createClass
。这是一个明智的决定,没有人会在 JavaScript 都内置了 class 时还会维护本身的类模型。
但就存在一个问题:ES6 class 不支持 mixin。除了不是 ES6 规范的一部分,Dan 已经在一篇 React 博客上发布的博文上详细讨论了 mixin 存在的其余问题。
minxins 的问题总结下来就是
createClass
API 会对两个 mixins 的 getInitialState
是否具备相同的 key 作检查,若是具备,则会发出警告,但该手段并不牢靠。因此,为了替代 mixin,React 社区中的很多开发者最终决定用高阶组件(简称 HOC)来作代码复用。在这个范式下,代码经过一个相似于 装饰器(decorator) 的技术进行共享。首先,你的一个组件定义了大量须要被渲染的标记,以后用若干具备你想用共享的行为的组件包裹它。所以,你如今是在 装饰 你的组件,而不是混入你须要的行为!
import React from 'react'
import ReactDOM from 'react-dom'
const withMouse = (Component) => {
return class extends React.Component {
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 {...this.props} mouse={this.state}/> </div> ) } } } const App = React.createClass({ render() { // 如今,咱们获得了一个鼠标位置的 prop,而再也不须要维护本身的 state const { x, y } = this.props.mouse return ( <div style={{ height: '100%' }}> <h1>The mouse position is ({x}, {y})</h1> </div> ) } }) // 主须要用 withMouse 包裹组件,它就能得到 mouse prop const AppWithMouse = withMouse(App) ReactDOM.render(<AppWithMouse/>, document.getElementById('app')) 复制代码
让咱们和 mixin 说再见,去拥抱 HOC 吧。
在 ES6 class 的新时代下,HOC 的确是一个可以优雅地解决代码重用问题方案,社区也已经普遍采用它了。
此刻,我想问一句:是什么驱使咱们迁移到 HOC ? 咱们是否解决了在使用 mixin 时遇到的问题?
让咱们看下:
另外一个 HOC 和 mixin 都有的问题就是,两者使用的是 静态组合 而不是 动态组合。问问你本身:在 HOC 这个范式下,组合是在哪里发生的?当组件类(如上例中的的 AppWithMouse
)被建立后,发生了一次静态组合。
你没法在 render
方法中使用 mixin 或者 HOC,而这恰是 React 动态 组合模型的关键。当你在 render
中完成了组合,你就能够利用到全部 React 生命期的优点了。动态组合或许微不足道,但兴许某天也会出现一篇专门探讨它的博客,等等,我有点离题了。😅
总而言之:使用 ES6 class 建立的 HOC 仍然会遇到和使用 createClass
时同样的问题,它只能算一次重构。
如今不要说拥抱 HOC 了,咱们不过在拥抱新的 mixin!🤗
除了上述缺陷,因为 HOC 的实质是包裹组件并建立了一个混入现有组件的 mixin 替代,所以,HOC 将引入大量的繁文缛节。从 HOC 中返回的组件须要表现得和它包裹的组件尽量同样(它须要和包裹组件接收同样的 props 等等)。这一事实使得构建健壮的 HOC 须要大量的样板代码(boilerplate code)。
上面我所讲到的,以 React Router 中的 withRouter
HOC 为例,你能够看到 props 传递、wrappedComponentRef、被包裹组件的静态属性提高(hoist)等等这样的样板代码,当你须要为你的 React 添加 HOC 时,就不得不撰写它们。
如今,有了另一门技术来作代码复用,该技术能够规避 mixin 和 HOC 的问题。在 React Training 中,称之为 “Render Props”。
我第一次见到 render prop 是在 ChengLou 在 React Europe 上 关于 react-motion 的演讲,大会上,他提到的 <Motion children>
API 能让组件与它的父组件共享 interpolated animation。若是让我来定义 render prop,我会这么定义:
一个 render prop 是一个类型为函数的 prop,它让组件知道该渲染什么。
更通俗的说法是:不一样于经过 “混入” 或者装饰来共享组件行为,一个普通组件只须要一个函数 prop 就可以进行一些 state 共享。
继续到上面的例子,咱们将经过一个类型为函数的 render
的 prop 来简化 withMouse
HOC 到一个普通的 <Mouse>
组件。而后,在 <Mouse>
的 render
方法中,咱们可使用一个 render prop 来让组件知道如何渲染:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
// 与 HOC 不一样,咱们可使用具备 render prop 的普通组件来共享代码
class Mouse extends React.Component {
static propTypes = {
render: PropTypes.func.isRequired
}
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.render(this.state)} </div>
)
}
}
const App = React.createClass({
render() {
return (
<div style={{ height: '100%' }}> <Mouse render={({ x, y }) => ( // render prop 给了咱们所须要的 state 来渲染咱们想要的 <h1>The mouse position is ({x}, {y})</h1> )}/> </div> ) } }) ReactDOM.render(<App/>, document.getElementById('app')) 复制代码
这里须要明确的概念是,<Mouse>
组件其实是调用了它的 render
方法来将它的 state 暴露给 <App>
组件。所以,<App>
能够随便按本身的想法使用这个 state,这太美妙了。😎
在此,我想说明,“children as a function” 是一个 彻底相同的概念,只是用 children
prop 替代了 render
prop。我挂在嘴边的 render prop
并非在强调一个 名叫 prop
的 prop,而是在强调你使用一个 prop 去进行渲染的概念。
该技术规避了全部 mixin 和 HOC 会面对的问题:
而且,render prop 也不会引入 任何繁文缛节,由于你不会 包裹 和 装饰 其余的组件。它仅仅是一个函数!若是你使用了 TypeScript 或者 Flow,你会发现相较于 HOC,如今很容易为你具备 render prop 的组件写一个类型定义。固然,这是另一个话题了。
另外,这里的组合模型是 动态的!每次组合都发生在 render 内部,所以,咱们就能利用到 React 生命周期以及天然流动的 props 和 state 带来的优点。
使用这个模式,你能够将 任何 HOC 替换一个具备 render prop 的通常组件。这点咱们能够证实!😅
一个更将强有力的,可以证实 render prop 比 HOC 要强大的证据是,任何 HOC 都能使用 render prop 替代,反之则否则。下面的代码展现了使用一个通常的、具备 render prop 的 <Mouse>
组件来实现的 withMouse
HOC:
const withMouse = (Component) => {
return class extends React.Component {
render() {
return <Mouse render={mouse => (
<Component {...this.props} mouse={mouse}/>
)}/>
}
}
}
复制代码
有心的读者可能已经意识到了 withRouter
HOC 在 React Router 代码库中确实就是经过**一个 render prop ** 实现的!
因此还不心动?快去你本身的代码中使用 render prop 吧!尝试使用具备 render prop 组件来替换 HOC。当你这么作了以后,你将再也不受困于 HOC 的繁文缛节,而且你也将利用到 React 给予的动态组合模型的好处,那是特别酷的特性。😎
Michael 是 React Training 的成员,也是 React 社区中一个多产的开源软件贡献者。想了解最新的培训和课程就[订阅邮件推送](subscribe to the mailing list) 并 在 Twitter 上关注 React Training。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。