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 相关文章推荐
Javascript 中文字符串处理额外注意事项
Nov 15 Javascript
传智播客学习之java 反射
Nov 22 Javascript
javascript将异步校验表单改写为同步表单
Jan 27 Javascript
如何实现移动端浏览器不显示 pc 端的广告
Oct 15 Javascript
详细分析JS函数去抖和节流
Dec 05 Javascript
js 取消页面可以选中文字的功能方法
Jan 02 Javascript
基于vue打包后字体和图片资源失效问题的解决方法
Mar 06 Javascript
webstorm中配置Eslint的两种方式及差异比较详解
Oct 19 Javascript
mpvue实现小程序签到金币掉落动画(api实现)
Oct 17 Javascript
Vue.js自定义指令学习使用详解
Oct 19 Javascript
微信小程序request请求封装,验签代码实例
Dec 04 Javascript
使用Typescript和ES模块发布Node模块的方法
May 25 Javascript
浅析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
PHP版自动生成文章摘要
2008/07/23 PHP
PHP全概率运算函数(优化版) Webgame开发必备
2011/07/04 PHP
Array.prototype.slice 使用扩展
2010/06/09 Javascript
JQuery里面的几种选择器 查找满足条件的元素$(&quot;#控件ID&quot;)
2011/08/23 Javascript
js选择并转移导航菜单示例代码
2014/08/19 Javascript
修复bash漏洞的shell脚本分享
2014/12/31 Javascript
Javascript非构造函数的继承
2015/04/27 Javascript
javascript字符串与数组转换汇总
2015/05/26 Javascript
Canvas实现动态的雪花效果
2017/02/13 Javascript
Node.js和Express简单入门介绍
2017/03/24 Javascript
js编写简单的计时器功能
2017/07/15 Javascript
JS实现闭包中的沙箱模式示例
2017/09/07 Javascript
浅谈webpack下的AOP式无侵入注入
2017/11/12 Javascript
原生JavaScript实现Ajax异步请求
2017/11/19 Javascript
深入理解Vue nextTick 机制
2018/04/28 Javascript
Vue的路由及路由钩子函数的实现
2019/07/02 Javascript
vue中的v-model原理,与组件自定义v-model详解
2020/08/04 Javascript
利用python画一颗心的方法示例
2017/01/31 Python
浅谈五大Python Web框架
2017/03/20 Python
Django实现自定义404,500页面教程
2017/03/26 Python
numpy中三维数组中加入元素后的位置详解
2019/11/28 Python
selenium中get_cookies()和add_cookie()的用法详解
2020/01/06 Python
Python读取表格类型文件代码实例
2020/02/17 Python
Django import export实现数据库导入导出方式
2020/04/03 Python
elf彩妆英国官网:e.l.f. Cosmetics英国(美国平价彩妆品牌)
2017/11/02 全球购物
将世界上最美丽的摄影作品转化为艺术作品:Photos.com
2017/11/28 全球购物
EMPHASIS艾斐诗官网:周生生旗下原创精品珠宝品牌
2020/12/17 全球购物
static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
2015/02/22 面试题
周鸿祎:教你写创业计划书
2013/12/30 职场文书
大学校园活动策划书
2014/02/04 职场文书
反四风个人对照检查材料思想汇报
2014/09/25 职场文书
廉洁自律心得体会2016
2016/01/13 职场文书
详解mysql三值逻辑与NULL
2021/05/19 MySQL
教你怎么用Python selenium操作浏览器对象的基础API
2021/06/23 Python
MYSQL 表的全面总结
2021/11/11 MySQL
python的netCDF4批量处理NC格式文件的操作方法
2022/03/21 Python