超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

React - Stateless Function Component

Stateless Function Component(SFC, 无状态组件)React v0.14.0 引入的一种新的创建组件的方式,这个方式大大提升了“纯组件”的编写速度和便捷性,而且还能有效提升组价的性能。

常规的组件

通常情况下,我们是使用 React.createClass() 或者 class MyComponent extends React.Component 创建组件,但是很多时候,我们需要的组件仅仅是一个简单的渲染数据的模板,这种情况下,这种方式创建的组件无疑十分的繁琐,可以看一个例子:

class Widget extends React.Component {
static propTypes = {
onClick: React.PropTypes.func.isRequired,
children: React.PropTypes.element.isRequired
}

_handleClick = (e) => {
//do sth
this.props.onClick(e);
}
render() {
returen (
<div onClick={this._handleClick}>
{this.props.children}
</div>
);
}
}

可以看到,这个组件没有任何的内部状态 state ,也没有任何的生命周期相关的操作,只接受一些参数,并根据参数渲染结果,任意相同的输入,必定会获得相同的输出结果,也不太需要关心其生命周期。

对于这种无状态的组件,完全可以使用 SFC 的方式创建。

Stateless Function Component

先来看下使用这种方式改写的代码:

const _handleClick = (event, onClick) => {
//do sth
this.props.onClick(event);
}

const Widget = (props, context) => <div onClick={e => _handleClick(e, props.onClick)}>{props.children}</div>;

Widget.propTypes = {
onClick: React.PropTypes.func.isRequired,
children: React.PropTypes.element.isRequired
}

不仅降低了代码的数量,而且提升了代码的可读性。

无状态组件实际上就是一个函数,函数接受两个参数,第一个是 props 第二个是 context ,返回一个 render 的内容。而且在渲染的时候由于是无状态的,可以省略将组件类实例化的过程,性能会更好。

This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps. In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations.

React 官方也建议采用这种模式创建简单的组件,对于有状态的组件,可以使用有状态的组件处理数据,把它们的 state 传递给无状态的组件进行内容的渲染:

class Container extends Component {
state = {
text: "init text"
}

_handleClick() {
this.setState({text: "click text"})
}

render() {
return (
<Widget onClick={this._handleClick}>{this.state.text}</Widget>
);
}
}

注意:由于 SFC 没有实例化的过程,因此不能使用 this 指针,也不能使用 ref 指针。

SFC 与函数

SFC 本质上是一个函数,所以定义的 SFC 大部分情况下即可以当做组件用,也可以当作函数使用。

如下面这个乍一看没有什么意义的组件,用来给元素包括一层 div

const DivWrapper = ({ className, children }) => <div className={className}>{ children }</div>;

在使用的时候我们可以直接当做 SFC 组件使用,也可以直接当做函数调用:

<DivWrapper className="wrapper">It used as SFC</DivWrapper>
// 等价于
// const createDivWrapper 是为了使函数命名更规范,没有其他实际意义
const createDivWrapper = DivWrapper;
createDivWrapper({className: 'wrapper', children: 'It used as function call' });

在上面这个示例中 实际是没有什么区别的,但是当我们的父元素使用 cloneElement 动态给其子元素注入属性的时候,会有很大区别,看下面这个例子:

const { Component, Children, cloneElement } = React;

// 这个无聊的组件为每个自己的子元素绑定了一个额外的 click 事件
class Parent extends Component {
render() {
return (
<div>
{
Children.map(
this.props.children,
(child, index) => cloneElement(child, {...child.props, onClick: () => console.log(child.props.children)})
)
}
</div>
);
}
}

// 使用
<Parent>
<DivWrapper className="wrapper">It used as SFC</DivWrapper>
{
createDivWrapper({className: 'wrapper', children: 'It used as function call' })
}
</Parent>

// 尝试分别点击 “It used as SFC” 和 “It used as function call”
// 点击 “It used as SFC” 不触发事件
// 点击 “It used as function call” 控制台打印日志 “It used as function call”

上面我们创建了一个组件,组件动态为自己的每个子元素注入一个新的点击事件,然后我们点击它的子元素发现,当作为 SFC 调用时并没有相应额外传递进来的 onClick 事件,而
作为函数调用却能够触发事件。

首先我们来这两种使用方式的区别:使用了 SFC ,Parent 的子元素是 Parent 组件,而是用了函数调用,Parent 的子元素是 div 元素。所以,当我们的 SFC 并没有提供对额外的属性响应处理时候,这个传递来的属性会丢失。

我们来个一个实际的例子,当我们在使用一些外部类库的时候,如rc-collapse,如果面板非常复杂,有时候会写出这样的代码:

// 创建一个复杂的title
const createComplexTitle = (props) => <ComplexTitle { ...props }/>;

// 创建一个复杂的title
const createComplexContent = (props) => <ComplexContent { ...props }/>;

const PanelWrapper = ({ data }) => {
// 进行数据处理判断
return (
<Collapse.Panel header={createComplexTitle(data)}>
{createComplexContent(data)}
</Collapse.Panel>
)
}
const createPanel = PanelWrapper;

const collapse1 = array => (
<Collapse>
{
array.map(item => <PanelWrapper data={item}/>)
}
</Collapse>
);
const collapse2 = array => (
<Collapse>
{
array.map(item => createPanel(item))
}
</Collapse>
);

如上例子,这个面板非常复杂,我们需要将其封装出来处理,这样就会有以上 collapse1collapse2 写法,而 collapse1 写法会丢失一些来自 Collapse 父组件传递的 props,除非我们添加了额外的处理:

// 可行但不推荐
const PanelCreater = ({ data, ...props }) => {
// 进行数据处理判断
return (
<Panel header={createComplexTitle(data)}, { ...props }>
{createComplexContent(data)}
</Panel>
)
};

改成这样后虽然能够处理额外的属性,但是会导致对组件的行为不可控制,我们不知道我们写出来的组件会最终变成什么样子,违背了组件设计的初衷,所以还是建议使用第二种写法。

大部分这样的组件都会对其子元素的类型进行要求,比如 react-router,它的 <Switch> 组件要求子元素必须为 <Route><Redirect>,因为它就是使用 cloneElementlocation 等属性写给路由对应的组件,如果我们在自己写这样类库的时候,也可以对 children 类型进行检查和限制,并能够对于不满足条件的子元素进行警告。

比如,我们来对 <Collapse> 组件的子元素进行限制:

import PropTypes from 'prop-types';

Collapse.propTypes = {
// ... other props
children: PropTypes.arrayOf(PropTypes.instanceOf(Collapse.Panel))
}

参考