引言
任何一种框架的诞生和流行都意味要学习框架内部本身实现所提倡的应用模式,如jQuery时代,立即执行表达式(IIFE), 深浅拷贝, 无new实例化(通过精巧原型链设计实现), 各种钩子函数(用于磨平不同浏览器兼容性差异),多种插件扩展方式(拷贝、原型)等等应用技巧或模式,无不深受前端工程师学习或面试题首选。MVVM时代亦然,当前流行的React、Vue、Angular三巨头无不深谙此道,引入或自创各种技巧或模式。
本文综合编译各类模式文章,学习并总结常见应用模式,以飨读者。
React篇
无状态组件和有状态组件
组件只接收输入props属性,最后返回视图内容,称之为无状态组件或纯组件。
示例:
简单的无状态按钮组件,只接受onClick一个属性
const Button = props =>
应用运行过程中,React组件维持并管理内部状态,称之为状态组件。
示例:
按钮容器组件,内部定义点击次数状态。
class ButtonCounter extends React.Component {
constructor() {
super()
this.state = { clicks: 0 }
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({ clicks: this.state.clicks + 1 })
}
render() {
return (
)
}
}
容器和展示组件
当和外部数据进行交互时,我们会把组件分成两类,容器负责接触React作用域外部的数据,如,连接Redux或Relay。
展示组件不依赖应用的任何部分,仅仅依赖其内部状态或接收属性。
示例:
接收属性的用户列表展示组件
const UserList = props =>
{props.users.map(u => (
- {u.name} — {u.age} years old
))}
可以通过容器组件更新用户列表组件
class UserListContainer extends React.Component {
constructor() {
super()
this.state = { users: [] }
}
componentDidMount() {
fetchUsers(users => this.setState({ users }))
}
render() {
return
}
}
高阶组件
高阶组件,简称为HOC, 在需要重用组件逻辑时很有用。
HOC是能够接收组件入参并且出参为新组件的JS函数。
举例,有一个可伸展的菜单组件,当用户点击菜单时显示其子代内容。
除了直接控制其父组件状态实现外,还可以创建一个简单通用HOC实现:
function makeToggleable(Clickable) {
return class extends React.Component {
constructor() {
super()
this.toggle = this.toggle.bind(this)
this.state = { show: false }
}
// 將切换显示逻辑抽象为HOC实现
toggle() {
this.setState(prevState => ({ show: !prevState.show }))
}
// 返回带切换逻辑的新组件
render() {
return (
{this.state.show && this.props.children}
)
}
}
}
使用JS装饰器可以將HOC逻辑应用到其他需要的组件上。
@makeToggleable
class ToggleableMenu extends React.Component {
render() {
return (
{this.props.title}
)
}
}
组件被装饰后可以接收任何子组件:
class Menu extends React.Component {
render() {
return (
Some content
Another content
More content
)
}
}
组成:
高阶函数组件, 接受被装饰组件,在返回新组件中维持通用抽象状态,新组件render渲染逻辑中执行状态判断
被修饰具体组件,显示最外层具体渲染组件
层次:
高阶函数组件(被修饰具体组件Class)
高阶组件的本质是將抽象状态逻辑维护至新组件中,具体组件作为新组件执行状态逻辑的最终返回值。
Redux的connect方法和React Router的withRouter函数,都是使用高阶组件实现。
渲染回调函数(函数子组件)
另一种实现复用组件逻辑的方法是將子组件变成函数,也被成为渲染回调函数。
这里以可伸展菜单为例使用此方法重写。
class Toggleable extends React.Component {
constructor() {
super()
this.toggle = this.toggle.bind(this)
this.state = { show: false }
}
toggle() {
this.setState(prevState => ({ show: !prevState.show }))
}
render() {
return this.props.children(this.state.show, this.toggle)
}
}
调用时传递函数作为Toggleable组件的子组件:
{(show, onClick) => (
First Menu
{show ?
Some content
: null
}
)}
如果想像HOC那样复用函数逻辑支持多个菜单,可以创建一个新组件使用Toggleable逻辑:
const ToggleableMenu = props =>
{(show, onClick) => (
{props.title}
{show && props.children}
)}
最终的Menu组件和HOC版本一样:
class Menu extends React.Component {
render() {
return (
Some content
Another content
More content
)
}
}
当我们在不考虑管理状态事,要改变渲染内容时,渲染回调很有用。
上例中,我们將渲染逻辑迁移成ToggleableMenu子函数组件, 但仍然在抽象组件Toggleable实现状态逻辑(维护show状态)。
渲染函数组成:
抽象组件,用于维护通用抽象状态,渲染方法调用子函数组件,生成最终渲染逻辑
子函数组件,接收抽象组件的状态或变量,实现真正渲染逻辑,渲染可从最外部传入
具体组件,调用抽象组件, 可传入最外部具体渲染组件
渲染函数组件层次:
<具体组件函数 传入具体渲染组件>
<抽象组件>
<子函数 />
抽象组件>
具体组件函数>
渲染函数的本质是抽象组件维护自身通用状态,同时隔离渲染逻辑(可能有很多判断或不同组件展示逻辑)到子函数中,子函数组件会接收抽象组件的部分状态或私有数据
,將抽象通用状态与组件渲染进行剥离,实现最大化组合复用。
译者注
本文将持续更新,因译者水平有限,观点或理解如有错误,欢迎指正交流
参考文章
React Component Patterns
Advanced React Component Patterns
React in Pattern
smart-and-dumb-components
react-higher-order-components-in-depth
function-as-child-components