React 无状态组件(Stateless Component) 与高阶组件


Posted in Javascript onAugust 14, 2018

无状态组件(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 的组合:

  1. defaultTheme 是从 Context 中解构出来的,如果 props 没有设定 theme,就将用 defaultTheme 替代。
  2. 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 } })
   }  
  }
 }
}

*** 专业的实现参看 https://github.com/kriasoft/react-decorators ***

那么,下面我们就可以创建出一个有机会响应窗口大小变化的 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,用 babel-plugin-react-transform 来做 jsx 转换等,那么当你在编辑器中修改无状态组件的源代码的时候,HMR 并不会在浏览器中自动载入修改后的代码。具体问题跟踪请参 https://github.com/gaearon/babel-plugin-react-transform/issues/57 。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js异步加载的三种解决方案
Mar 04 Javascript
JavaScript实现网页上的浮动广告的简单方法
Jun 14 Javascript
JavaScript charCodeAt方法入门实例(用于取得指定位置字符的Unicode编码)
Oct 17 Javascript
JavaScript实现梯形乘法表的方法
Apr 25 Javascript
详解javascript高级定时器
Dec 31 Javascript
jQuery 限制输入字符串长度
Jun 20 Javascript
基于bootstrap-datetimepicker.js不支持IE8的快速解决方法
Nov 07 Javascript
JavaScript中定义对象原型的两种使用方法
Dec 15 Javascript
BootStrap实现文件上传并带有进度条效果
Sep 11 Javascript
vue项目中vue-i18n和element-ui国际化开发实现过程
Apr 25 Javascript
vue input 输入校验字母数字组合且长度小于30的实现代码
May 16 Javascript
解决vue项目本地启动时无法携带cookie的问题
Feb 06 Vue.js
浅析Vue实例以及生命周期
Aug 14 #Javascript
快速解决angularJS中用post方法时后台拿不到值的问题
Aug 14 #Javascript
详解angular应用容器化部署
Aug 14 #Javascript
使用node.js实现微信小程序实时聊天功能
Aug 13 #Javascript
JQuery通过后台获取数据遍历到前台的方法
Aug 13 #jQuery
AngularJS实现与后台服务器进行交互的示例讲解
Aug 13 #Javascript
JS实现把一个页面层数据传递到另一个页面的两种方式
Aug 13 #Javascript
You might like
Linux下CoreSeek及PHP扩展模块的安装
2012/09/23 PHP
php获取目标函数执行时间示例
2014/03/04 PHP
PHP判断是否是微信打开,浏览器打开的方法
2018/03/14 PHP
用javascript实现给图片加链接
2007/08/15 Javascript
JQuery textlimit 显示用户输入的字符数 限制用户输入的字符数
2009/05/14 Javascript
JS+CSS实现DIV层的展开、收缩效果
2016/01/28 Javascript
完美JQuery图片切换效果的简单实现
2016/07/21 Javascript
AngularJS 中使用Swiper制作滚动图不能滑动的解决方法
2016/11/15 Javascript
微信小程序 动态的设置图片的高度和宽度详解及实例代码
2017/02/24 Javascript
nodejs简单实现TCP服务器端和客户端的聊天功能示例
2018/01/04 NodeJs
vue生成文件本地打开查看效果的实例
2018/09/06 Javascript
Three.js实现3D机房效果
2018/12/30 Javascript
JS实现的冒泡排序,快速排序,插入排序算法示例
2019/03/02 Javascript
微信小程序发布新版本时自动提示用户更新的方法
2019/06/07 Javascript
js实现点击按钮随机生成背景颜色
2020/09/05 Javascript
windows如何把已安装的nodejs高版本降级为低版本(图文教程)
2020/12/14 NodeJs
浅谈Python数据类型之间的转换
2016/06/08 Python
Python AES加密模块用法分析
2017/05/22 Python
浅谈python配置与使用OpenCV踩的一些坑
2018/04/02 Python
python3 打开外部程序及关闭的示例
2018/11/06 Python
python实发邮件实例详解
2019/11/11 Python
Django中密码的加密、验密、解密操作
2019/12/19 Python
pycharm无法导入本地模块的解决方式
2020/02/12 Python
Python使用requests xpath 并开启多线程爬取西刺代理ip实例
2020/03/06 Python
全面解析CSS Media媒体查询使用操作(推荐)
2017/08/15 HTML / CSS
英国演唱会订票网站:Ticket Selection
2018/03/27 全球购物
美国二手复古奢侈品包包购物网站:LXRandCo
2019/06/18 全球购物
Shell如何接收变量输入
2016/08/06 面试题
12月红领巾广播稿
2014/02/13 职场文书
2014年机关植树节活动方案
2014/02/27 职场文书
大学生暑期社会实践证明范本
2014/10/24 职场文书
党的群众路线整改落实情况汇报
2014/10/28 职场文书
2014年纪检部工作总结
2014/11/12 职场文书
纪念建国70周年演讲稿
2019/07/19 职场文书
为什么你写的height:100%不起作用
2021/05/10 HTML / CSS
UNION CREATIVE《Re:从零开始的异世界生活》雷姆手办
2022/03/20 日漫