React组件设计
组件分类
展示组件和容器组件
展示组件
容器组件
关注事物的展示
关注事物如何工作
可能包含展示和容器组件,并且一般会有DOM标签和css样式
可能包含展示和容器组件,并且不会有DOM标签和css样式
常常允许通过this.props.children传递
提供数据和行为给容器组件或者展示组件
对第三方没有任何依赖,比如store 或者 flux action
调用flux action 并且提供他们的回调给展示组件
不要指定数据如何加载和变化
作为数据源,通常采用较高阶的组件,而不是自己写,比如React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create()
仅通过属性获取数据和回调
null
很少有自己的状态,即使有,也是自己的UI状态
null
除非他们需要的自己的状态,生命周期,或性能优化才会被写为功能组件
null
下面是一个可能会经常写的组件,评论列表组件,数据交互和展示都放到了一个组件里面。
// CommentList.js
class CommentList extends React.Component {
constructor() {
super();
this.state = { comments: [] }
}
componentDidMount() {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: function(comments) {
this.setState({comments: comments});
}.bind(this)
});
}
render() {
return
- {this.state.comments.map(renderComment)}
}
renderComment({body, author}) {
return
{body}—{author};}
}
我们对上面的组件进行拆分,把他拆分成容器组件 CommentListContainer.js 和展示组件 CommentList。
// CommentListContainer.js
class CommentListContainer extends React.Component {
constructor() {
super();
this.state = { comments: [] }
}
componentDidMount() {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: function(comments) {
this.setState({comments: comments});
}.bind(this)
});
}
render() {
return ;
}
}
// CommentList.js
class CommentList extends React.Component {
constructor(props) {
super(props);
}
render() {
return
- {this.props.comments.map(renderComment)}
}
renderComment({body, author}) {
return
{body}—{author};}
}
优势:
展示和容器更好的分离,更好的理解应用程序和UI
重用性高,展示组件可以用于多个不同的state数据源
展示组件就是你的调色板,可以把他们放到单独的页面,在不影响应用程序的情况下,让设计师调整UI
迫使你分离标签,达到更高的可用性
有状态组件和无状态组件
下面是一个最简单的无状态组件的例子:
function HelloComponent(props, /* context */) {
return
}
ReactDOM.render(, mountNode)
可以看到,原本需要写“类”定义(React.createClass 或者 class YourComponent extends React.Component)来创建自己组件的定义(有状态组件),现在被精简成了只写一个 render 函数。更值得一提的是,由于仅仅是一个无状态函数,React 在渲染的时候也省掉了将“组件类” 实例化的过程。
结合 ES6 的解构赋值,可以让代码更精简。例如下面这个 Input 组件:
function Input({ label, name, value, ...props }, { defaultTheme }) {
const { theme, autoFocus, ...rootProps } = props
return (
htmlFor={name}
children={label || defaultLabel}
{...rootProps}
>
name={name}
type="text"
value={value || ''}
theme={theme || defaultTheme}
{...props}
/>
)}
Input.contextTypes = {defaultTheme: React.PropTypes.object};
无状态组件不像上述两种方法在调用时会创建新实例,它创建时始终保持了一个实例,避免了不必要的检查和内存分配,做到了内部优化。
无状态组件不支持 "ref"
高阶组件
高阶组件通过函数和闭包,改变已有组件的行为,本质上就是 Decorator 模式在 React 的一种实现。
当写着写着无状态组件的时候,有一天忽然发现需要状态处理了,那么无需彻底返工:)
往往我们需要状态的时候,这个需求是可以重用的。
高阶组件加无状态组件,则大大增强了整个代码的可测试性和可维护性。同时不断“诱使”我们写出组合性更好的代码。
高阶函数
function welcome() {
let username = localStorage.getItem('username');
console.log('welcome ' + username);
}
function goodbey() {
let username = localStorage.getItem('username');
console.log('goodbey ' + username);
}
welcome();
goodbey();
我们发现两个函数有一句代码是一样的,这叫冗余唉。(平时可能会有一大段代码的冗余)。
下面我们要写一个中间函数,读取username,他来负责把username传递给两个函数。
function welcome(username) {
console.log('welcome ' + username);
}
function goodbey(username) {
console.log('goodbey ' + username);
}
function wrapWithUsername(wrappedFunc) {
let newFunc = () => {
let username = localStorage.getItem('username');
wrappedFunc(username);
};
return newFunc;
}
welcome = wrapWithUsername(welcome);
goodbey = wrapWithUsername(goodbey);
welcome();
goodbey();
好了,我们里面的 wrapWithUsername 函数就是一个“高阶函数”。
他做了什么?他帮我们处理了 username,传递给目标函数。我们调用最终的函数 welcome的时候,根本不用关心 username是怎么来的。
举一反三的高阶组件
下面是两个冗余的组件。
import React, {Component} from 'react'
class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
username: ''
}
}