事件零碎
React基于Virtual DOM实现了一个SyntheticEvent(合成事件)层,咱们定义的处理器会接管一个SyntheticEvent对象的实例,它完全符合W3C规范,不会存在任何IE的兼容性问题。并且与原生的浏览器事件一样领有同样的接口,同样反对事件的冒泡机制,我门能够应用stopPropagation()和preventDefault()来中断它。如果须要拜访原生事件对象,能够应用nativeEvent属性。
合成事件的绑定形式
React事件的绑定形式与原生的HTML事件监听器属性很类似。
<button onClick={this.handleClick}>Test</button>
合成事件的实现机制
在React底层,次要对合成事件做了两件事:事件委派和主动绑定。
1.事件委派
React不会把事件处理函数间接绑定到实在的节点上,而是把所有事件绑定到构造的最外层,应用一个对立的事件监听器,这个事件监听器上维持了一个映射来保留所有组件外部的事件监听和处理函数。当组件挂载或卸载时,只是在这个对立的事件监听器上插入或删除一些对象;当事件产生时,首先被这个对立的事件监听器解决,而后在映射里找到真正的事件处理函数并调用。(实现原理:对最外层的容器进行绑定,依赖事件的冒泡机制实现委派。)这样简化了事件处理和回收机制,效率也有很大晋升。
2.自定绑定
在React组件中,每个办法的上下文都会指向该组件的实例,即自定绑定this为以后组件。而且React还会对这种援用进行缓存。在应用ES6 classes或者纯函数时,这种自定绑定就不复存在了,咱们须要手动实现this的绑定。
咱们来看几种绑定办法
bind办法
class App extends Component { constuctor() { super(props) this.handleClick = this.handleClick.bind(this) }}
箭头函数
它能够主动绑定此函数的作用域的this
class App extends Component { handleClick= () => {}}
在React中应用原生事件
class NativeEventDemo extends Component { componentDidMount() { this.refs.button.addEventListener('click', this.handleClick) } componentWillUnmout() { this.refs.button.removeEventListener('click', this.handleClick) }}
比照React合成事件与JavaScript原生事件
1.事件流传与阻止事件流传
浏览器原生DOM事件的流传能够分为3个阶段:事件捕捉阶段、指标对象自身的事件处理程序调用、事件冒泡。能够将e.addEventListener的第三个参数设置为true时,为元素e注册捕捉事件处理程序。事件捕捉在IE9以下无奈应用。事件捕捉在利用程序开发中意义不大,React在合成事件中并没有实现事件捕捉,仅仅反对了事件冒泡机制。
阻止原生事件流传须要应用e.stopPropagation,不过对于不反对该办法的浏览器(IE9以下)只能应用e.cancelBubble = true来阻止。而在React合成事件中,只须要应用stopPropagation()即可。阻止React事件冒泡的行为只能用于React合成事件零碎中,且没有方法阻止原生事件的冒泡。反之,原生事件阻止冒泡,能够阻止React合成事件的流传。
2.事件类型
React合成事件的事件类型是JavaScript原生事件类型的一个子集。它仅仅实现了DOM Level3的事件接口,并且对立了浏览器的兼容问题。有些事件React没有实现,或者受某些限度没方法去实现,如window的resize事件。
3.事件绑定形式
受到DOM规范影响,浏览器绑定原生事件的形式有很多种。React合成事件的绑定形式则简略很多
<button onClick={this.handleClick}>Test</button>
4.事件对象
在React合成事件零碎中,不存在兼容性问题,能够失去一个合成事件对象。
表单
在React中,所有数据都是状态,当然也包含表单数据。接下来咱们讲讲React是如何解决表单的。
利用表单组件
html表单中的所有组件在React的JSX都有实现,只是它们在用法上有些区别,有些是JSX语法上的,有些则是因为React对状态解决上导致的一些区别。
1.文本框
import React, { Component } from 'react';class App extends Component { constructor(props) { super(props); this.state = { inputValue: '', textareaValue: '' } } handleInputChange = (e) => { this.setState({ inputValue: e.target.value }); } handleTextareaChange = (e) => { this.setState({ textareaValue: e.target.value }) } render() { const { inputValue, textareaValue } = this.state; return ( <div> <p> 单行输入框: <input type="text" value={inputValue} onChange={this.handleInputChange}/> </p> <p> 多行输入框: <textarea type="text" value={textareaValue} onChange={this.handleTextareaChange}/> </p> </div> ) }}
在HTML中textarea的值是通过children来示意的,而在react中是用一个value prop来示意表单的值的。
2.单选按钮与复选框
在HTML中,用类型为radio的input标签示意单选按钮,用类型为checkbox的input标签示意复选框。这两种表单的value值个别是不会扭转的,而是通过一个布尔类型的checked prop来示意是否为选中状态。在JSX中这些是雷同的,不过用法上还是有些区别。
单选按钮的示例
import React, { Component } from 'react';class App extends Component { construtor(props) { super(props); this.state = { radioValue: '', } } handleChange = (e) => { this.setState( radioValue: e.target.value ) } render() { const { radioValue } = this.state; return ( <div> <p>gender:</p> <label> male: <input type="radio" value="male" checked={radioValue === 'male'} onChange={this.handleChange} /> </label> <label> female: <input type="radio" value="female" checked={radioValue === 'female'} onChange={this.handleChange} /> </label> </div> ) }}
复选按钮的示例
import React, { Component } from 'react';class App extends Component { constructor(props) { super(props) this.state = { coffee: [] } } handleChange = (e) => { const { checked, value } = e.target; let { coffee } = this.state; if (checked && coffee.indexOf(value) === -1) { coffee.push(value) } else { coffee = coffee.filter(i => i !== value) } this.setState({ coffee, }) } render() { const { coffee } = this.state; return ( <div> <p>请抉择你最喜爱的咖啡</p> <label> <input type="checkbox" value="Cappuccino" checked={coffee.indexOf('Cappuccino') !== -1} onChange={this.handleChange} /> Cappuccino </label> <br /> <label> <input type="checkbox" value="CafeMocha" checked={coffee.indexOf('CafeMocha') !== -1} onChange={this.handleChange} /> CafeMocha </label> </div> ) }}
3.Select组件
在HTML的select元素中,存在单选和多选两种。在JSX语法中,同样能够通过设置select标签的multiple={true}来实现一个多选下拉列表。
class App extends Component { constructor(props) { super(props) this.state = { area: '' } } handleChange = (e) => { this.setState({ area: e.target.value }) } render() { const { area } = this.state; return ( <select value={area} onChange={this.handleChange}> <option value='beijing'>北京</option> <option value='shangehai'>上海</option> </select> ) }}
select元素设置multiple={true}的示例
class App extends Component { constructor(props) { super(props) this.state = { area: ['beijing', 'shanghai'] } } handleChange = (e) => { const { options } = e.target; const area = Object.keys(options) .filter(i => options[i].selected === true) .map(i => options[i].value); this.setState({ area, }) } render () { const { area } = this.state; return ( <select multiple={true} value={area} onChange={this.handleChange}> <option value="北京">北京</option> <option value="上海">上海</option> </select> ) }}
在HTMl的option组件须要一个selected属性来示意默认选中的列表项,而React的解决形式是通过为select组件增加value prop来示意选中的option,在肯定水平上对立了接口。
实际上,也能够写成这种模式,不过开发体验就会差很多,React也会抛正告。
<select multiple={true} onChange={this.handleChange}> <option value="beijing" selected={area.indexOf('beijing') !== -1}>北京</option> <option value="shanghai" selected={area.indexOf('shanghai') !== -1}>上海</option></select>
受控组件
每当表单的状态发生变化,都会被写入到组件的state中,这种组件在React中被称为受控组件。在受控组件中,组件渲染出的状态与它的value或checked prop绝对应。React通过这种形式打消了组件的部分状态,使得利用的整个状态更加可控。
非受控组件
如果一个表单组件没有value prop(或checked prop),就能够称之为非受控组件。相应的你能够应用defaultValue和defaultChecked prop来示意组件的默认状态。
class App extends Compoent { constructor(props) { super(props) } handleSubmit = (e) => { e.preventDefault(); const { value } = this.refs.name; console.log(value) } render() { return ( <form onSubmit={this.handleSubmit}> <input ref="name" type="text" defaultValue="Hangzhou" /> <button type="submit">submit</button> </form> ) }}
在React中,非受控组件是一种反模式,它的值不受组件本身的state或props管制。通常,须要为其增加ref prop来拜访渲染后的底层DOM元素。
比照受控组件和非受控组件
受控组件和非受控组件的最大区别是:非受控组件的状态并不会受利用状态的管制,利用中也多了部分组件状态,而受控组件的值来自于组件的state。
1.性能上的问题
受控组件onChange后,调用setState会从新渲染,的确会有一些性能损耗。
2.是否须要事件绑定
受控组件须要为每个组件绑定一个change事件,并且定义一个事件处理器来同步表单值和组件的状态。
尽管如此,在React依然提倡应用受控组件,因为它能够使得利用的整个状态更加可控。
表单组件的几个重要属性
1.状态属性
React的form组件提供了几个重要的属性,用于展现组件的状态。
value: 类型为text的input组件、textarea组件以及select组件都借助value prop来展现利用的状态。
checked: 类型为radio或checkbox的组件借助值为boolean类型的checked prop来展现利用的状态。
selected: 该属性可作用于select组件上面的option上,React并不倡议应用功这种形式,举荐应用value.
2.事件属性
在状态属性发生变化时,会触发onChange事件属性。实际上,受控组件中的change事件与HTML DOM中提供的input事件更为相似。React反对DOM Level3中定义的所有表单事件。
款式解决
根本款式设置
React能够通过设置className prop给html设置class,也能够通过style prop给组件设置行内款式。
应用classnames库
咱们能够通过classnames库来设置html的类名
CSS Modules
CSS模块化的解决方案有很多,但次要有两类。
- Inline Style。这种计划彻底摈弃CSS,应用JavaScript或JSON来写款式,能给CSS提供JavaScript同样弱小的模块化能力。但毛病同样显著,它简直不能利用CSS自身的个性,比方级联、媒体查问等,:hover和:active等伪类解决起来比较复杂。另外,这种计划须要依赖框架实现,其中与React相干的有Radium、jsxstyle和react-style
- CSS Modules。仍旧应用CSS,但应用JavaScript来治理款式依赖。CSS Modules能最大化地联合现有CSS生态和JavaScript模块化能力,其API十分简洁。公布时仍旧编译出独自的JavaScript和CSS文件。当初,webpack css-loader内置CSS Modules性能。
1.CSS模块化遇到了哪些问题
CSS模块化重要的是解决好以下两个问题:CSS款式的导入与导出。灵便按需导入以便复用代码,导出时要暗藏外部作用域,免得造成全局净化。Sass、Less、PostCSS等试图解决CSS编程能力弱的问题,但并没有解决模块化这个问题。React理论开发须要的CSS相干问题有:
- 全局净化:CSS应用全局选择器机制来设置款式,长处是不便重写款式。毛病是所有的款式全局失效,款式可能被谬误笼罩。因而产生了十分俊俏的!important,甚至inline !important和简单的选择器权重计数表,进步犯错概率和应用老本。Web Component规范中的Shadow DOM能彻底解决这个问题,但它把款式彻底部分化,造成内部无奈重写款式,损失了灵活性。
- 命名凌乱:因为全局净化的问题,多人协同开发时为了防止款式抵触,选择器越来越简单,容易造成不同的命名格调,款式变多后,命名将更加凌乱。
- 依赖治理不彻底:组件应该互相独立,引入一个组件时,应该只引入它所须要的CSS款式。当初的做法是除了引入JavaScript,还要再引入它的CSS,而且Sass/Less很难实现对每个组件都编译出独自的CSS,引入所有模块的CSS又造成节约。JavaScript的模块化曾经十分成熟,如果能让JavaScript来治理CSS依赖是很好的解决办法,而webpack的css-loader提供了这种能力。
- 无奈共享变量:简单组件要应用JavaScript和CSS来独特解决款式,就会造成有些变量在JavaScript和CSS中冗余,而预编译语言不能提供跨JavaScript和CSS共享变量的这种能力。
- 代码压缩不彻底:对与十分长的类名压缩无能为力。
2.CSS Modules模块化计划
CSS Modules外部通过ICSS来解决款式导入和导出这两个问题,别离对应:import和:export两个新增的伪类。
:import("path/to/dep.css") { localAlias: keyFromDep;}:export { exportedKey: exportedValue;}
但间接应用这两个关键字编程太繁缛,我的项目中很少会间接应用它们,咱们须要的是用JavaScript来治理CSS的能力。联合webpack的css-loader,就能够在CSS中定义款式,在JavaScript文件中导出。
启用CSS Modules
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
加上modules即为启用,其中localIdentName是设置生成款式命名规定
上面咱们看看js是怎么引入CSS的:
/* button相干的所有款式 */.normal {}
import styles from './Button.css'buttonElm.outerHTML = `<button class=${styles.normal}>Submit</button>`
最终生成的HTML是这样的
<button class="button--normal-abc5436">Processing...</button>
这样class的名称根本就是惟一的。
CSS Modules对CSS中的class名都做了解决,应用对象来保留原class和混同后class的对应关系。通过这些简略的解决,CSS Modules实现了以下几点:
- 所有款式都是部分化的,解决了命名抵触和全局净化问题
- class名生成规定配置灵便,能够以此来压缩class名
- 只须要援用组件的JavaScript,就能搞定组件所有的JavaScript和CSS
- 仍然是CSS,学习老本简直为零
款式默认部分
应用CSS Modules相当于给每个class名外加了:local,以此来实现款式的部分化。如果咱们想切换到全局模式,能够应用:global包裹
.normal { color: green;}/* 与下面等价 */:local(.normal) { color: green;}/* 定义全局款式 */:global(.btn) { color: red;}/* 定义多个全局款式 */:global { .link { color: green; } .box { color: yellow; }}
应用composes来组合款式
对于款式复用,CSS Modules只提供了惟一的形式来解决——composes组合。
/* components/Button.css */.base { /* 所有通用的款式 */ }.normal { composes: base; /* normal其余款式 */}
此外,应用composes还能够组合内部文件中的款式
/* settings.css */.primary-color { color: #f40;}/* component/Button.css */.base { /* 所有通用款式 */ }.primary { composes: base; composes: $primary-color from './settings.css'}
对于大多数我的项目,有了composes后,曾经不再须要预编译处理器了。然而如果想用的话,因为composes不是规范的CSS语法,编译会报错,此时只能应用预处理本人的语法做款式复用了。
class命名技巧
CSS Modules的命名标准是从BEM扩大而来的。BEM把款式名分为3个级别
- Block: 对应模块名,如Dialog
- Element: 对应模块中的节点名 Confirm Button
- Modifier: 对应节点相干的状态,如disabled和highlight
如dialog__confirm-button--highlight。
实现CSS与JavaScript变量共享
:export关键字能够把CSS中的变量输入到JavaScript中
$primary-color: #f40;:export { primaryColor: $primary-color;}
// app.jsimport style from 'config.scss'console.log(style.primaryColor);
CSS Modules应用技巧
倡议遵循如下准则
- 不应用选择器,只应用class名来定义款式
- 不层叠多个class,只应用一个class把所有款式定义好
- 所有款式通过composes组合来实现复用
- 不嵌套
常见问题
1.如果在一个style文件应用同名class?
尽管编译后可能是随机码,但仍是同名的。
2.如果在style文件中应用了id选择器、伪类和标签选择器等呢?
这些选择器不被转换,一成不变地呈现在编译后的CSS中。也就是CSS Moudles只会转换class名相干的款式
CSS Modules联合历史遗留我的项目实际
1.内部如何笼罩部分款式
因为无奈预知最终的class名,不能通过个别选择器笼罩款式。咱们能够给组件要害节点加上data-role属性,而后通过属性选择器来笼罩款式。
// dialog.jsreturn ( <div className={styles.root} data-role="dialog-root"></div>);
// dialog.css[data-role="dialog-root"] { // override style}
2.如何与全局款式共存
批改webpack配置
module: { loaders: [{ test: /\.scss$/, exclude: path.resolve(__dirname, 'src/views'), loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true', }, { test: /\.scss$/, include: path.resolve(__dirname, 'src/styles'), loader: 'style!css!sass?sourceMap=true' }]}
/* src/app.js */import './styles/app.scss';import Component from './view/Component'/* src/views/Component.js */import './Component.scss'
CSS Modules联合React实际
import styles from './dialog.css';class Dialog extends Component { render() { return ( <div className={styles.root}></div> ) }}
如果不想频繁地输出styles.**,能够应用react-css-modules
组件间通信
父组件向子组件通信
父组件能够通过props向子组件传递须要的信息
子组件向父组件通信
有两种办法:1.利用回调函数。2.利用自定义事件机制:这种办法更通用,设计组件时思考退出事件机制往往能够达到简化组件API的目标。
在React中,能够应用任意一种办法,在简略场景下应用自定义事件过于简单,个别利用回调函数。
跨级组件通信
当须要让子组件跨级访问信息时,若像之前那样向更高级别的组件层层传递props,此时代码显得不那么优雅,甚至有些冗余。在React中,咱们还能够应用context来实现跨级父子组件间的通信。
class ListItem extends Component { static contextTypes = { color: PropTypes.string, } render () { return ( <li style={{ background: this.context.color }}></li> ) }}
class List extends Component { static childContextTypes = { color: PropTypes.string, } getChildContext() { return { color: 'red' } } render() { }}
React官网并不倡议大量应用context,因为当组件结构复杂的时候,咱们很难晓得context是从哪传过来的。应用context比拟好的场景是真正意义上的全局信息且不会更改,例如界面主题、用户信息等。总体的准则是如果咱们真的须要它,那么倡议写成高阶组件来实现。
没有嵌套关系的组件通信
没有嵌套关系的,那只能通过能够影响全局的一些机制去思考。之前讲的自定义事件机制不失为一种上佳的办法。
咱们在处理事件过程中须要留神,在componentDidMount事件中,如果组件挂载实现,再订阅事件;当组件卸载的时候,在componentWillUnmount事件中勾销事件的订阅。
对于React应用的场景,EventEmitter只须要单例就能够了
import { EventEmitter } from 'events';export default new EventEmitter();
import emitter from './events';emitter.emit('ItenChange', entry)
class App extends Component { componentDidMount() { this.itemChange = emitter.on('ItemChange', (data) => { console.log(data) }) } componentWillUnmount() { emitter.removeListener(this.itemChange) }}
一般来说,程序中呈现多级传递或跨级传递,那么要个从新扫视一下是否有更正当的形式。Pub/Sub的模式可能也会带来逻辑关系的凌乱。
跨级通信往往是反模式的,应该尽量避免仅仅通过例如Pub/Sub实现的设计思路,退出强依赖与约定来进一步梳理流程是更好的办法。(如应用Redux)
组件间形象
经常有这样的场景,有一类性能须要被不同的组件专用,此时就波及形象的话题。咱们重点探讨两种:mixin和高阶组件
封装mixin办法
const mixin = function(obj, mixins) { const newObj = obj; newObj.prototype = Object.create(obj.prototype); for (let prop in mixins) { if (mixins.hasOwnProperty(prop)) { newObj.prototype[prop] = mixins[prop] } }}const BigMixin = { fly: () => { console.log('fly'); }}const Big = function() { console.log('new big');}consg FlyBig = mixin(Big, BigMixin)const flyBig = new FlyBig();flyBig.fly(); // => 'fly'
对于狭义的mixin办法,就是用赋值的形式将mixin对象里的办法都挂载到原对象上,来实现对对象的混入。
看到上述实现,你可能会联想到underscore库中的extend或lodash库中的assign办法,或者说ES6中的Object.assign()办法。MDN上的解释是把任意多个源对象所领有的本身可枚举属性复制给指标对象,而后返回指标对象。
在React中应用mixin
React在应用createClass构建组件时提供了mixin属性,比方官网封装的PureRenderMixin
import React from 'react';import PureRenderMixin from 'react-addons-pure-render-mixin';React.createClass({ mixins: [PureRenderMixin], render() {}})
mixins数组也能够减少多个mixin,其若mixin办法之间有重合,对于一般办法,在控制台里会报一个ReactClassInterface的谬误。对于生命周期办法,会将各个模块的生命周期办法叠加在一起程序执行。
mixin为组件做了两件事:
- 工具办法。如果你想共享一些工具类办法,能够定义它们,间接在各个组件中应用。
- 生命周期继承,props与state合并,mixin也能够作用在getInitialState的后果上,作state的合并,而props也是这样合并的。
ES6 Classes与decorator
ES6 classes模式构建组件,它并不反对mixin。decorator语法糖能够实现class上的mixin。
core-decorators库为开发者提供了一些实用的decorator,其中实现了咱们正想要的@mixin
import { getOwnPropertyDescriptors } from './private/utils';const { defineProperty } = Object;function handleClass(target, mixins) { if (!mixins.length) { // throw error; } for(let i = 0, l = mixins.length; i < l; i ++) { const descs = getOwnPropertyDescriptors(mixins[i]) for (const key in descs) { if (!(key in target.prototype)) { defineProperty(target.prototype, key, descs[key]) } } }}export default function mixin(...mixins) { if (typeof mixins[0] === 'function') { return handleClass(mixins[0], []) } else { return target => { return handleClass(target, mixins) } }}
原理也很简略,它将每一个mixin对象的办法都叠加到target对象的原型上以达到mixin的目标。这样就能够用@mixin来做多个重用模块的叠加了。
const PureRender = { shouldComponentUpdate() {}}const Theme = { setTheme() {}}@mixin(PureRender, Theme)class MyComponent extends Component { render() {}}
mixin的逻辑和最早实现的简略逻辑很类似,之前间接给对象的prototype属性赋值,但这里用了getOwnPropertyDescriptor和defineProperty这两个办法,有什么区别呢?
这样实现的好在于definedProperty这个办法,也就是定义和赋值的区别,定义是对已有的定义,赋值是笼罩已有的定义。前者并不会笼罩已有办法,但后者会。实质上与官网的mixin办法都很不一样,除了定义方法级别不能笼罩外,还得加上对生命周期办法的继承,以及对state的合并。
decorator还有作用在办法上的,它能够管制办法的自有属性,也能够作decorator的工厂办法。
mixin的问题
mixin存在很多问题,曾经被官网弃用了,由高阶组件代替。
1.毁坏的原有组件的封装
咱们晓得mixin办法会混入办法,给原有组件带来新的个性,比方mixin中有一个renderList办法,给咱们带来了渲染List的能力,但它也可能带来新的state和props,这意味着组件有一些"不可见"的状态须要咱们去保护,但咱们在应用的时候并不分明。此外renderList中的办法会调用组件中办法,但很可能被其余mixin截获,带来很多不可知。
2.不同mixin的命名抵触
3.减少复杂性
咱们设计一个组件,引入PopupMixin的mixin,这样就给组件引进了PopupMixin生命周期办法。当咱们再引入HoverMixin,将有更多的办法被引进。当然咱们能够进一步形象出TooltipMixin,将两个整合在一起,但咱们发现它们都有compomentDidUpdate办法。过一段时间,你会发现它的逻辑曾经简单到难以了解了。
咱们写React组件时,首先思考的往往是繁多的性能、简洁的设计和逻辑。当退出性能的时候,能够持续管制组件的输出和输入。如果说因为复杂性,咱们一直退出新的状态,那么组件会变得十分难保护。
高阶组件
高阶函数是函数式编程中的一个基本概念,这种函数承受函数作为输出,或是输入一个函数。
高阶组件相似高阶函数,它承受React组件作为输出,输入一个新的React组件。
高阶组件让咱们的代码更具备复用性、逻辑性与形象个性,它能够对render办法作劫持,也能够管制props和state。
实现高阶组件的办法有两种:
- 属性代理:通过被包裹的React组件来操作props
- 反向继承:继承于被包裹的React组件
1.属性代理
const MyContainer = (WrappedComponent) => { class extends Component { render() { return <WrappedComponent {...this.props} /> } }}
这样,咱们就能够通过高阶组件来传递props,这种办法即为属性代理。
这样组件就能够一层层地作为参数被调用,原始组件就具备了高阶组件对它的润饰。放弃了单个组件封装性同时还保留了易用性。当然,也能够用decorator来转换
@MyContainerclass MyComponent extends Component { render() {}}export default MyComponent
上述执行生命周期的过程相似于堆栈调用:
didmount -> HOC didmount -> (HOCs didmount) -> (HOCs will unmount) -> HOC will unmount -> unmount
从性能上,高阶组件一样能够做到像mixin对组件的管制,包含管制props、通过refs应用援用、形象state和应用其余元素包裹WrappedComponent.
1.管制props
咱们能够读取、减少、编辑或是移除从WrappedComponent传进来的props,但须要小心删除与编辑重要的props。咱们应该尽可能对高阶组件的props作新的命名以避免混同。
例如,咱们须要减少一个新的prop:
const MyContainer = (WrappedComponent) => { class extends Component { render() { const newProps = { text: newText, }; return <WrappedComponent {...this.props} {...newProps} />; } }}
2.通过refs应用援用
const MyContainer = (WrappedComponent) => { class extends Component { proc(wrappedComponentInstance) { wrappedComponentInstance.method(); } render() { const props = Object.assign({}, this.props, { ref: this.proc.bind(this), }) return <WrappedComponent {...props} /> } }}
这样就能够不便地用于读取或减少实例的props,并调用实例的办法。
3.形象state
高阶组件能够将原组件形象为展现型组件,拆散外部状态
const MyContainer = (WrappedComponent) => { class extends Component { constructor(props) { super(props) this.state = { name: '' } this.onNameChange = this.onNameChange.bind(this) } onNameChange(event) { this.setState({ name: event.target.value }) } render() { const newProps = { name: { value: this.state.name, onChange: this.onNameChange, } } return <WrappedComponent {...this.props} {...newProps}> } }}
这样就无效地形象了同样的state操作。
4.应用其余元素包裹WrappedComponent
这既能够是为了加款式,也能够是为了布局
const MyContainer = (WrappedComponent) => { class extends Component { render() { return ( <div style={{ display: 'block' }}> <WrappedComponent {...this.props} /> </div> ) } }}
反向继承
const MyContainer = (WrappedComponent) => { class extends WrappedComponent{ render() { return super.render() } }}
高阶组件返回的组件继承于WrappedComponent,因为被动地继承了WrappedComponent,所有调用都会反向,这也是这种办法的由来。
因为依赖于继承的机制,HOC的调用程序和队列是一样的
didmount->HOC didmount->(HOCs didmount)->will unmount->HOC will unmount->(HOCs will unmount)
在反向继承办法中,高阶组件能够应用WrappedComponent援用,这意味着它能够应用WrappedComponent的state、props、生命周期、和render。但它不能保障残缺的子组件树被解析。
它有两大特点
1.渲染劫持
高阶组件能够管制WrappedComponent的渲染过程。能够在这个过程中在任何React元素输入的后果中读取;减少、批改。删除props,或读取或批改React元素树,或条件显示元素树,又或是用款式管制包裹元素树。
如果元素树中包含了函数类型的React组件,就不能操作组件的子组件。
条件渲染示例
const MyContainer = (WrappedComponent) => { class extends WrappedComponent { render() { if (this.props.loggedIn) { return super.render(); } else { return null; } } }}
对render的输入后果进行批改
const MyContainer = (WrappedComponent) => { class extends WrappedComponent { render() { const elementsTree = super.render(); let newProps; if (elementsTree && elementsTree.type === 'input') { newProps = { value: 'may the force be with you' } } const props = Object.assign({}, elementsTree.props, newProps); const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children); return newElementsTree; } }}
2.管制state
高阶组件能够读取、批改或删除WrappedComponent实例中的state,如果需要的话,也能够减少state。但这样做,可能会让WrappedComponent组件外部状态变得一团糟。大部分高阶组件都应该限度读取或减少state,尤其是后者,能够通过重命名state,以避免混同
const MyContainer = (WrappedComponent) => { class extends WrappedComponent { render() { return ( <div> <h2>HOC Debugger Component</h2> <p>Props</p><pre>{JSON.stringify(this.props, null, 2)}</pre> <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre> {super.render()} </div> ) } }}
在这个例子中,显示了WrappedComponent的props和state,不便咱们调试。
组件命名
高阶组件失去了原始的diplayName,咱们应该为高阶组件命名
HOC.displayName = `HOC(${getDisplayName(WrappedComponent)})`
或
class HOC extends ... { static displayName = `HOC(${getDisplayName(WrappedComponent)})`}
function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'}
组件参数
function HOCFactoryFactory(...params) { return function HOCFactory(WrappedComponent) { return class HOC extends Component { render() { return <WrappedComponent {...this.props} /> } } }}
组合式组件开发实际
咱们屡次提到,应用React开发组件时利用props传递参数。也就是说,用参数来配置组件时咱们最罕用的封装形式。随着场景发生变化,组件的状态也发生变化,咱们必须一直减少props去应答变动,此时便会导致props的泛滥,而在拓展中又必须保障组件向下兼容,只增不减,使组件的可维护性升高。
咱们就能够利用上述高阶组件的思维,提出组件组合式开发模式,无效地解决了配置式所存在的一些问题。
1.组件再拆散
SelectInput、SearchInput与List三个颗粒度更细的组件能够组合成功能丰富的组件,而每个组件能够是纯正的、木偶式的组件。
2.逻辑再形象
组件中雷同交互逻辑和业务逻辑也应该形象
// 实现SearchInput与List的交互const searchDecorator = WrappedComponent => { class SearchDecorator extends Component { constructor(props) { super(props) this.handleSearch = this.handleSearch.bind(this) } handleSearch(keyword) { this.setState({ data: this.props.data, keyword, }) this.props.onSearch(keyword) } render() { const { data, keyword } = this.state; return ( <WrappedComponent {...this.props} data={data} keyword={keyword} onSearch={this.handleSearch} /> ) } } return SearchDecorator;}// 实现List数据申请const asyncSelectDecorator = WrappedComponent => { class AsyncSelectDecorator extends Component { componentDidMount() { const { url, params } = this.props; fetch(url, { params }).then(data => { this.setState({ data }) }) } render() { return ( <WrappedComponent {...this.props} data={this.state.data} /> ) } } return AsyncSelectDecorator;}
最初,咱们用compose将高阶组件层层包裹,将页面和逻辑完满联合在一起
const FinalSelector = compose(asyncSelectDecorator, searchDecorator, selectedItemDecorator)(Selector)
组件性能优化
从React的渲染过程中,如何避免不必要的渲染是最须要去解决的问题。
针对这个问题,React官网提供了一个便捷的办法来解决,那就是PureRender
纯函数
纯函数由三大准则形成
1.给定雷同的输出,它总是返回雷同的输入
2.过程没有副作用(咱们不能扭转内部状态)
3.没有额定的状态依赖
纯函数也十分不便进行办法级别的测试以及重构,能够让程序具备良好的扩展性及适应性。
PureRender
PureRender中的Pure指的就是组件满足纯函数的条件,即组件的渲染是被雷同的props和state渲染进而失去雷同的后果。
1.PureRender实质
官网在晚期为开发者提供了一个react-addons-pure-render-mixin的插件,其原理为从新实现了shouldComponentUpdate生命周期办法,让以后传入的props和state与之前的作浅比拟,如果返回false,那么组件就不会执行render办法。(若做深度比拟,也很耗性能)
PuerRender源码中,只对新旧props作了浅比拟,以下是shallowEqual的示例代码
function shallowEqual(obj, newObj) { if (obj === newObj) { return true; } const objKeys = Object.keys(obj); const newObjKeys = Object.keys(newObj); if (objKeys.length !== newObjKeysl.length) { return false; } return objKeys.every(key => { return newObj[key] === obj[key]; })}
3.优化PureRender
如果说props或state中有以下几种类型的状况,那么无论如何,它都会触发PureRender为true。
3.1间接为props设置对象或数组
援用的地址会扭转
<Account style={{ color: 'black' }} />
防止这个问题
const defaultStyle = {};<Account style={{ this.props.style || defaultStyle }} />
3.2设置props办法并通过事件绑定在元素上
class MyInput extends Component { constructor(props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange(e) { this.props.update(e.target.value) } render() { return <input onChange={this.handleChange} /> }}
3.3设置子组件
对于设置了子组件的React组件,在调用shouldComponentUpdate时,均返回true。
class NameItem extends Component { render() { return ( <Item> <span>Arcthur</span> </Item> ) }}
<Item children={React.createElement('span', {}, 'Arcthur')}/>
怎么防止反复渲染笼罩呢?咱们在NameItem设置PureRender,也就是提到父级来判断。
Immutable
在传递数据时能够间接应用Immutable Data来进一步晋升组件的渲染性能。
JavaScript中的对象个别是可变的,因为应用了援用赋值,新的对象简略地援用了原始对象,扭转新的对象将影响到原始对象。
应用浅拷贝或深拷贝能够防止批改,但这样做又造成了内存和CPU的节约。
1.Immutable Data一旦被创立,就不能再更改数据,对Immutable对象进行批改,增加或删除操作,都会返回一个新的Immutable对象。Immutable实现的原理是长久化的数据结构,也就是应用旧数据创立新数据,要保留旧数据同时可用且不变,同时为了防止深拷贝把所有节点复制一遍带来的性能损耗,Immutable应用了构造共享,即如果对象树中一个节点发生变化,只批改这个节点和受它影响的父节点,其余节点则进行共享。
Immutable的长处
1.升高了可变带来的复杂度。
可变数据耦合了time和value的概念,造成了数据很难被回溯
function touchAndLog (touchFn) { let data = { key: '1' } touchFn(data); console.log(data.key)}
若data是不可变的,能打印的后果是什么。
2.节俭内存
Immutable应用的构造共享尽量复用内存。没有援用的对象会被垃圾回收。
import { map } from 'immutable';let a = Map({ select: '1', filter: Map({ name: '2' }),});let b = a.set('select', 'people');a === ba.get('filter') === b.get('filter')// true
3.撤销/重做,复制/粘贴,甚至工夫旅行这些性能都很容易实现。因为每次数据都是不一样的,那么只有把这些数据放到一个数组里存储起来,就能自在回退。
4.并发平安
数据天生是不可变的,后端罕用的并发锁就不须要了,然而当初并没有用,因为个别JavaScript是单线程运行的。
5.拥抱函数式编程
Immutable自身就是函数式编程中的概念,只有输出统一,输入必然统一。
应用Immutable的毛病
容易与原生对象混同是应用Immutale的过程中遇到的最大问题。
上面给出了一些方法
1.应用FlowType或TypeScript动态类型查看工具
2.约定变量命名规定,如Immutable类型对象以$$结尾
3.应用Immutable.fromJS而不是Immutable.Map或Immutable.List来创建对象,这样能够防止Immutable对象和原生对象间的混用
Immutable.js
两个Immutable对象能够应用===来比拟,这样是间接比拟内存地址,其性能最好。然而即便两个对象的值是一样的,也会返回false。
Immutable提供了Immutable.is来作"值比拟",Immutable比拟的是两个对象的hasCode或valueOf,因为Immutable外部应用了trie数据结构来存储,只有两个对象的hasCode相等,值就是一样的。这样的算法防止了深度遍历比拟,因而性能十分好。
Immutable与cursor
这里的cursor和数据库中的游标是齐全不同的概念。因为Immutable数据个别嵌套十分深,所以为了便于拜访深层数据,cursor提供了能够间接拜访这个深层数据的援用:
let data = Immutable.fromJS({ a: { b: { c: 1 } } });let cursor = Cursor.from(data, ['a', 'b'], newData => { // 当cursor或其子cursor执行更新时调用 console.log(newData)})// 1cursor.get('c');cursor = cursor.update('c', x => x + 1)// 2cursor.get('c');
Immutable与PureRender
React做性能优化时最罕用的就是shouldComponentUpdate,应用深拷贝和深比拟是十分低廉的抉择。而使应用Immutable.js,===和is是高效地判断数据是否变动的办法。
import { is } from 'immutable'shouldComponentUpdate(nextProps, nextState) { const thisProps = this.props || {}; const thisState = this.state || {}; if (Object.keys(thisProps).length !== Object.keys(nextProps).length || Object.keys(thisState).length !== Object.keys(nextState).length) { return true; } for (const key in nextProps) { if (nextProps.hasOwnProperty(key) && !is(thisProps[key], nextProps[key])) { return true; } } for (const key in nextState) { if (nextState.hasOwnProperty(key) && !is(thisState[key], nextState[key])) { return true; } }}
Immutable与setState
React倡议把this.state当作不可变的,因而批改前须要做一个深拷贝
import _ from 'lodash';class App extends Component { this.state = { data: { time: 0 } } handleAdd() { let data = _.cloneDeep(this.state.data); data.time = data.time + 1; this.setState({ data }); }}
应用Immutable后,操作变得很简略
import { Map } from 'immutable';class App extends Component { this.state = { data: Map({ time: 0 }) } handleAdd() { this.setState(({ data }) => { data: data.update('times', v => v + 1) }) }}
Immutable能够给利用带来极大的性能晋升。
key
如果每一个组件是一个数组或迭代器的话,那么必须有一个惟一的key prop。它是用来标识以后项的唯一性的。如果应用index,它相当于一个随机键,无论有没有雷同的项,更新都会渲染。如果key雷同,react会抛正告,且只会渲染第一个key。
若有两个子组件须要渲染,能够用插件createFragment包裹来解决。
react-addons-perf
react-addons-perf通过Perf.start和Perf.stop两个API设置开始和完结的状态来作剖析,它会把各组件渲染的各阶段的工夫统计进去,而后打印出一张表格。
参考
《深刻React技术栈》