函数式编程,对应的是声明式编程,声明式编程的本质的lambda验算(是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包)
React元素
React元素使用JSON格式的代码:
const DeleteAccount = () => ({
type: 'div',
props: {
children: [
{
type: 'p',
props: {
children: 'Are you sure?'
}
},
{
type: DangerButton,
props: {
children: 'Confirm'
}
},
{
type:Button,
props: {
children: 'Cancel'
}
}
]
}
})
React.createElement表示的代码:
var DeleteAccount = function() {
return React.createElment(
'div',
null,
React.createElment(
'p',
null,
'Are you sure?'
),
React.createElment(
DangerButton,
null,
'Confirm',
),
React.createElment(
Button,
{ color: 'blue' },
'Cancel'
)
)
}
组件
组件封装的时候,组件的几项规范标准:
- 基本的封装性
- 简单的声明周期呈现
- 明确的数据流动
Web Component 的4个组成部分:
- HTML Templates定义了模板的概念
- Custom Elements定义了组件的展现形式
- Shadow DOM定义了组件的作用域范围,可以包括样式
-
HTML Imports提出了新的引入方式
React组件的构建:
Web Component通过自定义元素的方式实现组件化,而React的本质就是关心元素的构成,React组件即为组件元素。组件元素被描述成纯粹的JSON对象。
React组件基本上由3个部分组成 -- 属性(props),状态(state)以及生命周期
React所有组件继承自顶层类 React.Component。它的定义非常简洁,只是初始化了 React.Component方法,声明了props、context、refs等,并在原型上定义了setState和forceUpdate方法。内部初始化的声明周期方法与createClass方法使用的是同一个方法创建的。这类组件会创建实例对象。
无状态组件:使用无状态函数构建的组件,只传入props和context两个参数,不存在state,没有生命周期方法。它创建时始终保持了一个实例,避免了不必要的检查和内存分配,做到了内部优化
state
注意:setState是一个异步方法,一个生命周期内所有的setState方法会合并操作。
智能组件(smart Component):内部(State)更新数据,控制组件渲染
木偶组件(dumb COmponent):外部(props)更新数据,控制组件渲染
props
props是properties的缩写。props是React用来组件之间互相联系的一种机制,通俗的说就像方法的参数一样。
子组件prop
在React中有一个重要且内置的prop--children,它代表组件的子组件集合。组件props
在组件的prop中某一个属性,我们传入节点,然后渲染这个节点,和 children 类似用function prop与父组件通信
propTypes
用于规范props的类型与必须的状态。
生命周期
分为两类:
- 当组件在挂载或卸载时
- 当组件接收新的数据时,即组件更新时
组件的挂载,这个过程主要做组件状态初始化
// 推荐使用这个例子作为模板写初始化组件
class App extends Component {
// props类型检查
static propTypes = {
// ...
};
// 默认类型
static defaultProps = {
// ...
};
constructor(props) { super(props);
this.state = {
// ...
};
}
componentWillMount() {
// ...
}
componentDidMount() {
// ...
}
render() {
return <div>This is a demo.</div>;
}
}
propTypes和defaultProps声明成静态属性,意味着从类外面也可以访问他们,比如:App.propTypes和App.defaultProps
组件的卸载
componentWillUnmount
这个方法中,我们常常会执行一些清理方法,如事件回收或是清除定时器
数据更新过程
更新过程指父组件向下传递props或组件自身执行setState方法时发生的一系列更新动作。
import React, { Component, PropTypes } from 'react';
class App extends Component {
componentWillReceiveProps(nextProps) {
// this.setState({})
}
shouldComponentUpdate(nextProps, nextState) {
// return true;
}
componentWillUpdate(nextProps, nextState) {
// ...
}
componentDidUpdate(prevProps, prevState) {
// ...
}
render() {
return <div>This is a demo.</div>;
}
}
如果state更新了,那么会依次执行 shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate
外来组件prop传递进来使组件更新
componentWillReceiveProps(nextProps) {
if ( 'activeIndex' in nextProps ) {
this.setState({
activeIndex: nextProps.activeIndex
})
}
}
整体流程
React与DOM
ReactDOM
ReactDOM中的API很少,只有findDOMNode、unmountComponentAtNode和render
- findDOMNode
DOM真正被添加到HTML中的生命周期方法是componentDidMount和componentDidUpdate方法。ReactDOM提供findDOMNode:DOMElement findDOMNode(ReactComponent component),假设当前组件加载完时获取当前DOM,则可以使用findDOMNode
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
componentDidMount() {
// this 为当前组件的实例
const dom = ReactDOM.findDOMNode(this);
}
render() {}
}
- render
ReactComponent render(
ReactElement element,
DOMElement container,
[function callback]
)
该方法把元素挂在到container中,并返回element的实例(即refs引用)。如果是无状态组件,render会返回null。组件挂载完毕时,callback就会被调用。
与 render 相反,React 还提供了一个很少使用的 unmountComponentAtNode 方法来进行
卸载操作
refs
ref -> reference
ref返回一个实例对象,也可以是一个回调函数,focus的巧妙实现,就是使用回调函数
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
if (this.myTextInput !== null) {
this.myTextInput.focus(); }
}
render() {
return (
<div>
<input type="text" ref={(ref) => this.myTextInput = ref} /> <input
type="button"
value="Focus the text input" onClick={this.handleClick}
/> </div>
);
React之外的DOM操作
DOM操作可以归纳为对DOM的增、删、改、查。“查”指的是对DOM属性、样式的查看,比如DOM的位置、宽、高等信息。如果要调用HTML5 Audio/Video的play方法和input的focus方法,这时智能使用相应的DOM方法来实现。
比如Popup等组件
componentDidUpdate(prevProps, prevState) {
if ( !this.state.isActive && prevState.isActive ) {
document.removeEventListener('click', this.hidePopup);
}
if( this.state.isActive && !prevState.isActive ) {
document.addEventListener('click', this.hidePopup)
}
}
componentWillUnmount() {
document.removeEventListener('click', this.hidePopup)
}
if( !this.isMounted() ) {
return false;
}
const node = ReactDOM.findDOMnode(this);
const target = e.target || e.srcElement;
const isInside = node.contains(target);
if ( this.state.isActive && !isInside ) {
this.setState({
isActive: false
})
}
计算DOM的尺寸(即位置信息),提供width和height这样的工具函数
function width(el) {
const styles = el.ownerDocument.defaultView.getComputedStyle(el, null);
const width = parseFloat(styles.width.indexOf('px') !== -1 ? styles.width : 0);
const boxSizing = styles.boxSizing || 'content-box';
if (boxSizing === 'border-box') {
return width;
}
const borderLeftWidth = parseFloat(styles.borderLeftWidth);
const borderRightWidth = parseFloat(styles.borderRightWidth);
const paddingLeft = parseFloat(styles.paddingLeft);
const paddingRight = parseFloat(styles.paddingRight);
return width - borderRightWidth - borderLeftWidth - paddingLeft - paddingRight;
}
漫谈React
事件系统
Virtual DOM在内存中是以对象的形式存在的。React基于Virtual DOM实现了一个SyntheticEvent(合成事件)层,我们所定义的事件处理器会接收到一个SyntheticEvent对象的实例,它完全符合W3C标准。
所有事件自动绑定到最外层上,如需访问原生事件对象,可以使员工nativeEvent属性
合成事件实现机制
主要对合成事件做了两件事:事件委派和自动绑定。
- 事件委派
事件代理机制。它并不会把事件处理函数直接绑定到真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;;当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。 - 自动绑定
每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件
只绑定,不传参,可以使用一种便捷的方案--双冒号语法,babel已经支持
<button onClick={this.handleClick.bind(this)}></button>
<button onClick={::this.handleClick}></button>
// 箭头函数。自动绑定定义此函数作用域的this,因此我们需要对它使用bind方法
const handleClick = (e) => {
console.log(e)
}
<button onClick={this.handleClick}></button>
// 或者
<button onClick={() => this.handleClick()}></button>
React中使用原生事件
componentDidMount会在组件已经完成安装并且在浏览器中存在真实的DOM后调用,因此我们就可以完成原生事件的绑定。
值得注意的是,在 React 中使用 DOM 原生事件时,一定要在组件卸载时手动移除,否则很可能出现内存泄漏的问题。在原生事件中阻止冒泡行为,可以阻止React合成事件的传播,阻止React事件冒泡的行为,不能阻止原生事件的冒泡。
对于无法使用React合成事件的场景,我们还需要使用原生事件来完成。
对比React合成事件与JavaScript原生事件
- 事件传播与阻止事件传播
DOM事件传播三个阶段:事件捕获阶段、目标对象本身的事件处理程序调用以及事件冒泡。
React只有事件冒泡 - 事件类型
- 事件绑定方式
原生事件绑定方式
1、直接在DOM元素绑定
<button onclick='alert(1)'></button>
2、JS中,通过为元素的事件属性赋值的方式实现绑定
el.onClick = e => { }
3、通过事件监听函数实现绑定(addEventListener)
React只有一种
<button onClick={this.handleClick.bind(this)}></button>
4、事件对象
React不存在兼容性问题,DOM事件对象在W3C和IE标准中存在差异。
表单
受控组件
每当表单的状态发生改变时,都会被写入到组件的state中,这种组件在React中被称为受控组件(controlled Component),其上绑定了一个change事件,表单的数据元素组件的state,并通过props传入。
Reaact受控组件更新state的流程:
(1)可以通过在初始state中设置表单的默认值
(2)每当表单的值繁盛变化时,调用onChange事件处理器
(3)事件处理器通过合成事件对象e拿到改变后的状态,并更新应用的state
(4)setState触发视图的重新渲染,完成表单组件值的更新
非受控组件
如果一个表单组件没有value props(单选按钮和复选框对应的是checked prop)时,就可以称为非受控组件。
受控组件和非受控组件的最大区别是:非受控组件的状态并不会受应用状态的控制,应用中也多了局部组件状态,而受控组件的值来自于组件的 state。
表单组件的几个重要属性
- 状态属性
- value:类型为text的input,textarea组件以及select组件
- checked:类型为radio,checkbox的组件借助boolean类型的selected
- selected:该属性可作用于select组件下面的option上
样式处理
基本样式处理
- 自定义组件建议支持className prop,让用户使用时添加自定义样式
- 设置行内样式时需要使用对象
使用 classnames库来才做类。
CSS Modules
Vjeux抛出了React开发中遇到的一系列CSS相关问题,有以下几点:
- 全局污染
- 命名混乱
- 依赖管理不彻底
- 无法共享变量
- 代码压缩不彻底
启用CSS Modules
// webpack.config.js 的css-loader
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
- 使用composes来组合样式,进行样式的复用
.base { /*所有通用样式*/ }
.normal {
compose: base;
/* normal 其他样式 */
}
- class命名技巧
BEM命名:
1、Block:对应模块名,如Dialog
2、Element:对应模块中的节点名Confirm Button
3、Modifier:对应节点相关的状态,如disableed
组件中通信
父组件向子组件通信
通过props向子组件传递需要的信息子组件向父组件通信
1、利用回调函数
2、利用自定义事件机制
跨级组件通信
使用context
父组件中定义 ChildContext
// 父组件
static childContextTypes = {
color: PropTypes.string
}
getChildContext() {
return {
color: 'red'
}
}
// 子组件
static contextTypes = {
color: PropTypes.string
}
<p style={ { background: this.context.color } }></p>
组件间抽象
广义中的mixin方法,就是用赋值的方式将mixin对象里的方法都挂载到原对象上,来实现对对象的混入。
ES6 Classes与decorator:
ES并没有改变JavaScript面向对象方法基于原型的本质。decorator是运用在运行时的方法。
高阶组件
higher-order function(高阶函数)在函数式 编程中是一个基本的概念,它描述的是这样一种函数:这种函数接受函数作为输入,或是输出一 个函数。
高阶组件(higher-order component),类似于高阶函数,它接受React 组件作为输入,输出一 个新的 React 组件。
实现高阶组件的方法有如下两种:
- 属性代理(props proxy)。高阶组件通过被包裹的React组件来操作props
- 反向继承(inheritance inversion)。高阶组件继承于被包裹的React组件。
- 属性代理
import React, { Componet } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
// 使用高阶组件
class MyComponent extends Compnent {
// ...
}
export default MyContainer(MyComponent);
// 使用decorator
@MyContainer
class MyComponent extends Component {
render() {}
}
export default MyComponent;
上述执行声明周期的过程类似堆栈调用:
didmount -> HOC didmount -> (HOCs didmount) -> (HOCs will unmount) -> HOC will unmount -> unmount
高阶组件的功能:
- 控制props
- 通过refs使用引用
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) =>
class extends Component {
// 得到实例对象
proc(wrappedComponentInstance) {
// 执行实例方法
wrappedComponentInstance.method()
}
render() {
const props = Object.assign({}, this.props, {
ref: this.proc.bind(this)
})
return <WrappedCompoent {...props} />
}
}
- 抽象State
- 使用其他元素包裹WrappedComponent
- 反向继承
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
return super.render()
}
}
}
他的HOC调用顺序和队列是一样的:
didmount -> HOC didmount -> (HOCs didmount) -> will unmount -> HOC will didmount -> (HOCs will unmount)
它有两个比较大的特点:
- 渲染劫持
渲染劫持指的就是高阶组件可以控制 WrappedComponent 的渲染过程,并渲染各种各样的结果。
// 渲染劫持的示例
const MyContainer = (WrappedComponent) =>
class extends WrappedComponet {
render() {
if ( this.props.loggedIn ) {
return super.render()
} else {
return null;
}
}
}
- 控制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>
);
}
}
- 组件命名
react-redux库中已经实现了HOC.displayName =
HOC(${getDisplayName(WrappedComponent)})``
可以使用recompose库
- 组件参数
import React, { Component } from 'react';
function HOCFactoryFactory(...param) {
// 可以做一些改变params的事
return function HOCFactory(WrappeComponent) {
return class HOC extend Component {
render() {
return <WrappedComponent {...this.props} />
}
}
}
}
// 使用
HOCFactoryFactory(params)(WrappedComponent)
@HOCFactoryFactory(params)
class WrappedComponent extends React.Component { }
组件叠加使用
多个组件方法,叠加使用,可以利用 compose方法包裹
const FinalSelector = compose(asyncSelectDecorator,searchDecorator, selectedItemDecorator)(Selector);
性能优化
1、纯函数
2、pureRender
官方在早期就为开发者提供了名为 react-addons-pure-render-mixin 的插件。其原理为重新实现了 shouldComponentUpdate生命周期方法,让当前传入的 props 和 state 与之前的作浅比较,如果返回 false,那么组件就不会执行 render 方法。
3、Immutable
4、key
5、react-addons-perf
量化以上所做的性能优化的效果。
动画
React Transition
生命周期
- componentWillAppear
- componentDidAppear
- componentWillEnter
- componentDidEnter
- componentWillLeave
- componentDidLeave
componentWillxxx 在什么时候触发很容易判断,只需在componentWillReceiveProps 中对 this.props.children 和nextProps.children 做一个比较即可。而 componentDidxxx 要何时触发呢?
可以给 componentWillxxx 提供一个回调函数,用来执行componentDidxxx。
缓动函数
自动化测试
Jest