React 无状态组件(Stateless Component) 与高阶组件
无状态组件(stateless component) 是 react 0.14 之后推出的,大大增强了编写 react 组件的方便性,也提升了整体的渲染性能。
无状态组件 (stateless component)
function hellocomponent(props, /* context */) { return <div>hello {props.name}</div> } reactdom.render(<hellocomponent name="sebastian" />, mountnode)
hellocomponent 第一个参数是 props,第二个是 context。最后一句也可以这么写:
reactdom.render(hellocomponent{ name:"sebastian" }, 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 ( <label htmlfor={name} children={label || defaultlabel} {...rootprops} > <input name={name} type="text" value={value || ''} theme={theme || defaulttheme} {...props} /> )} input.contexttypes = {defaulttheme: react.proptypes.object};
这个 input 组件(仅仅是示例)直接实现了 label/inputtext 的组合:
- defaulttheme 是从 context 中解构出来的,如果 props 没有设定 theme,就将用 defaulttheme 替代。
- autofocus 需要被传递到底层的 inputtext 而不能同时遗留给 label,因此会先通过 { theme, autofocus, ...rootprops } = props 拿出来。
无状态组件用来实现 server 端渲染也很方便,只要避免去直接访问各种 dom 方法。
无状态组件与组件的生命周期方法
我们可以看到,无状态组件就剩了一个 render 方法,因此也就没有没法实现组件的生命周期方法,例如 componentdidmount, componentwillunmount 等。那么如果需要让我们的 input 组件能够响应窗口大小的变化,那么该如何实现呢?这其实还是要引入“有状态的组件”,只不过这个“有状态的组件”可以不仅仅为 "input" 组件服务。
const executionenvironment = require('react/lib/executionenvironment') const defaultviewport = { width: 1366, height: 768 }; // default size for server-side rendering function withviewport(composedcomponent) { return class viewport extends react.component { state = { // server 端渲染和单元测试的时候可未必有 dom 存在 viewport: executionenvironment.canusedom ? { width: window.innerwidth, height: window.innerheight } : defaultviewport } componentdidmount() { // server 端渲染是不会执行到 `componentdidmount` 的,只会执行到 `componentwillmount` window.addeventlistener('resize', this.handlewindowresize) window.addeventlistener('orientationchange', this.handlewindowresize) } componentwillunmount() { window.removeeventlistener('resize', this.handlewindowresize) window.removeeventlistener('orientationchange', this.handlewindowresize) } render() { return <composedcomponent {...this.props} viewport={this.state.viewport}/> } handlewindowresize() { const { viewport } = this.state if (viewport.width !== window.innerwidth || viewport.height !== window.innerheight) { this.setstate({ viewport: { width: window.innerwidth, height: window.innerheight } }) } } } }
*** 专业的实现参看 ***
那么,下面我们就可以创建出一个有机会响应窗口大小变化的 input 组件:
const sizeableinput = withviewport(input) reactdom.render(<sizeableinput name="username" label="username" {...props} />, mountnode)
withviewort 作为一个 "高阶组件" 可不仅仅是为了 input 服务的。它可以为你需要的任何组件添加上 viewport 属性,当窗口大小变化时,触发重绘。
如果你用过 redux,那么应该也熟悉 "connect decorator" 的用法。"connect decorator" 也是一个高阶组件,因此,你可以继续来“拼凑”:
const usernameinput = connect( state => ({ value: state.username }) )(sizeableinput)
高阶组件的存在有两个好处:
- 当写着写着无状态组件的时候,有一天忽然发现需要状态处理了,那么无需彻底返工:)
- 往往我们需要状态的时候,这个需求是可以重用的,例如上面的 withviewport,今后可以用来给其他组件(无论是否是无状态组件)添加 viewport 属性。
高阶组件加无状态组件,则大大增强了整个代码的可测试性和可维护性。同时不断“诱使”我们写出组合性更好的代码。
无状态组件不支持 "ref"
有一点遗憾的是无状态组件不支持 "ref"。原理很简单,因为在 react 调用到无状态组件的方法之前,是没有一个实例化的过程的,因此也就没有所谓的 "ref"。
ref 和 finddomnode 这个组合,实际上是打破了父子组件之间仅仅通过 props 来传递状态的约定,是危险且肮脏,需要避免。
无状态组件尚不支持 babel-plugin-react-transform 的 hot module replacement
如果你是用 webpack 以及 hmr,用 来做 jsx 转换等,那么当你在编辑器中修改无状态组件的源代码的时候,hmr 并不会在浏览器中自动载入修改后的代码。具体问题跟踪请参 。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: .NET/C#实现识别用户访问设备的方法