github 的地址 欢迎 star
前言
在我为一次会议准备技术演讲的过程中,我想花点时间和大家分享我在设计 React 组件模式上一些感悟。组件是 React 的核心,理解它对于设计一个良好的项目架构是十分重要的。
文中的图表是从一个非常棒的演讲(来自 Michael Chan)得到的。我强烈推荐去看他的视频
什么是组件
根据 React 官网的介绍,组件可以让你把 UI 划分为独立,可复用的部件,你只需独立的考虑每个部件的构建设计。
当你第一次运行 npm install react
,在本地就可以获取到 React 源码(路径./node_modules/react/umd/react.development.js
),它可以看做一个大的组件,提供了一系列的接口。React 组件和 JavaScript 函数是类似,组件接受一个称为 “props” 的输入,返回描述(声明)用户界面的 React 元素。你只需要告诉 React 你用户界面的样子,React 就会帮你把剩下的事情完成(保持 DOM 和数据同步),渲染出界面。这也是 React 被称为声明式库的原因。
声明式就是假如你要去一个地方的时候,你选择了打的,只需要告诉司机你的目的地,司机就会自己带你到目的地。而命令式是相反的,是需要你自己驾车去目的地的。
组件的 API
那么,当你下载 React,得到了哪些 API 呢?它们有5个:
- render
- state
- props
- context
- lifecycle events
虽然组件提供了一份完整,方便利用的 API,但很自然的一些组件,你会使用一部分 API,另外的组件使用和之前不完全相同的API。一般就把组件划分为有状态 (Stateful) 组件和无状态 (stateless) 组件。有状态组件通常用到了 render, state
以及( lifecycle events)生命周期钩子,无状态组件通常使用了 render, props以及context
。
组件设计模式
通常的设计模式有:
- 容器 (Container) 组件
- 展示 (Presentational) 组件
- 高阶 (Higher-Order) 组件
- 渲染回调(Render Callbacks)
容器 (Container) 组件
蓝色的代表容器组件,其里面灰的表示展示组件容器组件是同外部数据进行交互(通信),然后渲染其相应的子组件 --Jason Bonta
容器组件是数据或逻辑层,你能够使用上面提到的有状态的 API。使用生命周期钩子,能直接连接到状态管理 store,例如 Redux 或 Flux
,能通过 props 传递数据和回调给其相应的子组件。
容器组件的 render
方法中返回的是由多个展示子组件组成的 React 元素。为了能访问所有的有状态的 API,容器组件必须用 ES6 的 class
声明组件,而不是用函数声明。
如下,声明了叫 Greeting
的组件,它有 state, 一个生命周期钩子 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>
);
}
}
此时,这个组件是一个有状态的组件。为了使 Greeting
组件变成容器组件,可以将用户界面拆分为展示组件,将在下面说明。
展示(Presentational)组件
展示组件能够使用props, render以及 context
(无状态的 API),它其实就是可以用函数声明的无状态组件:
const GreetingCard = (props) => {
return (
<div>
<h1>Hello! {props.name}</h1>
</div>
)
}
展示组件仅仅从 props 中接受数据和回调,props 是由容器组件或者它的父组件产生的。
蓝色的代表展示组件,灰色的表示容器组件 用容器和展示组件分别同时地封装了逻辑与 UI 展示,这样才能得到理想的组件:
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>
);
}
}
如上面所见,我把UI展示的部分从 Greeting
移动到了一个函数式无状态组件。当然这只是一个简单的例子,但在更复杂的应用中基本上也是这么处理的。
高阶组件(HOC)
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
这是一种为任意组件复用某个组件逻辑而提供的强大模式。就比如react-router-v4 和 Redux
。在react-router-v4
中,使用withRouter()
,你的组件就能通过 props 继承react-router
中的一些方法。在 redux
中也是一样的,connect({})()
方法就能把 actions
和 reducer
传入组件中。
来看这个例子:
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()
包裹了我的组件。然后在生命周期钩子 componentDidMount()
中,可以通过 this.props.location.pathname
中的值更新 state
。我的组件就通过 props 获取到了 react-router-v4 的方法。还有很多其他的例子。
Render callbacks(渲染回调)
与 HOC 类似的,render callbacks 或 render props
也是共享或复用组件逻辑的强大模式。尽管更多的开发者倾向于通过 HOC 复用逻辑,使用 render callbacks 还是有一定原因和优势的--在一次 Michael Jackson “绝不再写另一个的HOC”极好的解释了。其中涉及到一些关键的地方,render callbacks 能够减少命名空间的冲突以及更好地说明代码逻辑是来自哪里。
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>
);
}
}
在 Count
组件的 render
中嵌套了 this.props.children
方法,并把 this.state
作为参数传给它。在 App
组件中,我用 Counter
包裹了它,在 App
中就可以获取到 Counter
的数据方法等逻辑。{state => ()}
就是 render callback。我自动地获取到了 Counter
中的 state。
谢谢你的阅读
欢迎大家留言建议,以上就是我对 React 组件设计模式的看法!
上面没有提到 render props,可以查看官网介绍例子
当然 React V16.8.0 添加了 hooks新的API,用函数也能实现有状态的组件了,大家可以查看官网了解
最后,推荐大家关注 React 作者之一 Dan 的博客,编写有弹性的组件的4个原则。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢!