原文地址: https://medium.com/teamsubchannel/react-component-patterns-e7fb75be7bb0
做者: William Whatley
摘要:本文介绍了4种组件类型:容器组件、展现组件、高阶组件和渲染回调。
今天,我想花一点时间来分享我学习React组件模式的经验。这个想法来源于一次聚会时的技术交流。组件是React的核心,所以有必要去理解如何使用它们。react
如下的例子都脱胎于Michael Chan gave on React component patterns这个视频的思想。我很是推荐大家看一看。git
reactjs.org上说:“组件可以将你的界面分割成独立且可复用的小块,而这些小组件都是互相独立的。”github
当你第一次执行npm install react
命令的时候,你获得了一个组件以及相关的API。与JS的函数相似,一个组件接收输入,叫作props
。而后返回React元素,做用是描述UI界面的样式。这就是React做为声明式API的表现形式,由于你只须要告诉它你想要展现的UI,剩下的React都会帮你完成。npm
对于声明式API的概念,你能够想象成滴滴打车的场景——告诉司机你的目的地,接着让他来完成开车的工做。而命令式API不一样,你将完成全部任务,既是乘客,也是司机。segmentfault
当你在安装好React后,获得了5类API: api
尽管写组件时能够把上面全部的API都使用一遍,可是你很快会发现一些组件只须要用到某些API,而另外一些组件也只会使用另外一些特定的API。而这两类组件每每被划分为有状态与无状态组件两大类。有状态的组件会有表明性地使用有状态API:render、state和生命周期。但无状态组件只会使用render、props和context。 数组
以上就是咱们在介绍组件模式前所须要的知识铺垫。组件模式是设计组件的最佳实践,最初是把组件切割成数据/逻辑层和UI/展现层。经过拆分组件的职责,可以设计出更容易复用、更内聚的组件,从而组装成复杂的UI界面。这个特性对于构建可扩展的应用时是很是重要的。react-router
经常使用的组件模式有:dom
“容器组件的做用是获取数据和渲染子组件。”——Jason Bonta 函数
蓝色表明容器组件,灰色表明展现用的子组件
容器组件使用了有状态的API,封装了数据逻辑层。经过使用生命周期,你能够链接Redux或Flux等状态管理库,而后把数据和回调函数看成props传递给子组件。在容器组件的render方法中,你能够用子展现组件来拼装UI界面。容器组件每每都设计成一个类组件,而不是纯函数组件,为的就是可以使用全部有状态的API。
在下面的例子中,咱们有一个名为Greeting的有状态的类组件,包括componentDidMount()
和render
方法。
class Greeting extends React.Component { constructor() { super(); this.state = { name: "", }; } componentDidMount() { // AJAX this.setState(() => { return { name: "William", }; }); } render() { return ( <div> <h1>Hello! {this.state.name}</h1> </div> ); } }
这时,这个组件仅仅是一个有状态的类组件。为了让它成为一个真正的容器组件,咱们要把UI部分放进一个展现组件中。下面就来说讲展现组件。
展现组件使用到props、render和context这些无状态API,而且能够写成更简洁优雅的函数式无状态组件。
const GreetingCard = (props) => { return ( <div> <h1>Hello! {props.name}</h1> </div> ) }
展现组件只能从父级组件或容器组件传来的props中接收数据和回调函数。
蓝色表明展现组件,而灰色表明容器组件。
容器组件和展现组件合并起来后,封装成了一个真正被使用的组件:
const GreetingCard = (props) => { return ( <div> <h1>{props.name}</h1> </div> ) } class Greeting extends React.Component { constructor() { super(); this.state = { name: "", }; } componentDidMount() { // AJAX this.setState(() => { return { name: "William", }; }); } render() { return ( <div> <GreetingCard name={this.state.name} /> </div> ); } }
如你所见,我把Greeting类组件中的UI部分移除,放入一个无状态的函数组件中。固然,这只是一个简单的例子,但对于复杂的应用来讲,这是最基本的作法。
高阶组件就是一个把组件看成参数,而且返回新组件的函数。
它的强大之处在于可以给任意数量的组件提供数据源,而且能够被用来实现逻辑复用。用react-router-v4或Redux举个例子。使用react-router-v4时,你可使用withRouter()
来继承传给组件的props。而使用Redux时,你经过使用connect({})()
来把actions看成props传递给子组件。
虚线表示的是高阶组件,它可以返回一个新的组件。
看看这个例子:
import {withRouter} from 'react-router-dom'; class App extends React.Component { constructor() { super(); this.state = {path: ''} } componentDidMount() { let pathName = this.props.location.pathname; this.setState(() => { return { path: pathName, } }) } render() { return ( <div> <h1>Hi! I'm being rendered at: {this.state.path}</h1> </div> ) } } export default withRouter(App);
当导出个人组件时,我使用react-router-v4提供的withRouter()
方法把它包裹起来。而在App的componentDidMount()
生命周期中,我经过this.props.location.pathname
来更新状态。在被withRouter()
包裹后,个人类组件如今能够经过props访问react-router-v4的方法,就像this.props.location.pathname
。像这样的例子,实在是不胜枚举。
与高阶组件相似,渲染回调或者说渲染props都是用来实现组件逻辑复用功能。相比较于大多数开发者使用的高阶组件,渲染回调也有本身的优点。具体优点能够阅读Michael Jackson所写的这个视频——《Never write another HOC.》。视频中所讲的关键点就是渲染回调可以减小命名空间的冲突而且解释逻辑的来源。
蓝色虚线表明渲染回调函数。
class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0, }; } increment = () => { this.setState(prevState => { return { count: prevState.count + 1, }; }); }; render() { return ( <div onClick={this.increment}>{this.props.children(this.state)}</div> ); } } class App extends React.Component { render() { return ( <Counter> {state => ( <div> <h1>The count is: {state.count}</h1> </div> )} </Counter> ); } }
在上面的Counter类中,我在render
里使用了this.props.children
,而后把this.state
看成参数传给这个函数。以后在App类中,我把想要展现的组件用Counter组件包裹起来,这样就能使用Counter的代码逻辑了。render
函数的返回结果是代码28行,在那里我经过{state => ()}
自动获取到Counter的state。
我很乐意接受你们的意见来使我成长。我对React组件模式的看法还不够成熟,因此我也算是在写做中学习吧。
查看更多我翻译的Medium文章请访问:
项目地址: https://github.com/WhiteYin/translation
SF专栏: https://segmentfault.com/blog/yin-translation