React知识点整理

初入React

JSX语法

  • 定义标签时,只允许被一个标签包裹
  • 标签一定要闭合
  • 注释被{}包起来

React组件

  • 无状态组件创建时始终保持了一个实例
  • 有状态组件创建几次组件就会创建几次实例

React数据流

state
  • setState是异步方法
props
  • props本身是不可变的(readonly
  • defaultProps静态变量可以定义props默认配置(默认类型
static defaultProps = {
    classPrefix: 'tabs', 
    onChange: () => {},
};

  • React中有一个内置的prop:children,代表组件的子组件集合
  • JavaScript不是强类型的语言,React对此做了改进,propTypes用于规范props的类型与必要状态(类型检查)。
static propTypes = {
  tab: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.node,
  ]).isRequired,
  order: React.PropTypes.string.isRequired,
  disble: React.PropTypes.bool,
};

React生命周期

分为挂载、渲染、卸载几个阶段

挂载或卸载

主要做组件状态初始化

挂载
挂载流程
卸载
卸载流程
数据更新

指父组件向下传递props或组件自身执行setState方法时发生的一系列更新动作

import React, {Component, PropTypes} from 'react';

class App extends Component {
    componentWillReceiveProps(nextProps){
        //this.setState({})
    }

    shouldComponentUpdate(nextProps, nextState) {

    }

    componentWillUpdate(nextProps, nextState) {

    }

    componentDidUpdate(prevProps, prevState) {

    }

    render() {

    }
}

组件自身state更新
组件自身state更新

不能在componentWillUpdate里执行setState

父组件更新props而更新
  • 在shouldComponentUpdate之前先执行componentWillReceiveProps。
  • 该方法可作为React在props传入后,渲染之前setState。
componentWillReceiveProps(nextProps) {

}

React与DOM

refs

refs是React组件中特殊的props,可以附加到任何一个组件上。组件被调用时会新建一个该组件的实例,refs则指向这个实例。

  • refs放在原生DOM组件中可以得到其DOM节点
  • refs放在React组件中则获得组件的实例,可以调用该组件的实例方法

漫谈React

事件系统

在React底层,主要对合成事件做了两件事:事件委派和自动绑定。

在React中使用原生事件

React生命周期方法中,componentDidMount会在组件已经完成安装并且在浏览器中存在真实的DOM后调用,此时可以完成原生事件的绑定。

import React, {Component} from 'react';

class NativeEventDemo extends Component {
    componentDidMound() {
        this.refs.button.addEventListener('click', e => {
            handleClick(e);
        });
    }

    handerClick(e) {
        console.log(e);
    }

    componentWillUnMount() {
        this.refs.button.removeEventListener('click');
    }

    render() {
        return <button ref="button">Test</button>
    }
}

注:在React中使用DOM原生事件时,一定要在组件卸载时手动卸除,否则很可能会出现内存泄漏的问题。

对比React合成事件与JavaScript原生事件
  • 浏览器原生DOM事件的传播可以分为三个阶段:事件捕获阶段、事件处理以及事件冒泡。React的合成事件并没有实现事件捕获,仅仅支持了事件冒泡机制。阻止事件传播e.preventDefault();
  • React合成事件的时间类型是JavaScript原生事件类型的一个子集。

组件间通信

父组件向子组件通信

父组件通过props向子组件传递需要的信息

import React, {Component} from 'react';

function ListItem({value}) {
    return (
        <li>
            <span>{value}</span>
        </li>
    );
}

function List({list, title}) {
    return(
        <div>
            <ListTitle title={title} />
            <ul>
                {this.props.list.map((entry, index) => {
                    <ListItem key={`list-${index}`} value={entry.text}>

                })}
            </ul>
        </div>
    );
}

子组件向父组件通信
  • 利用回调函数
  • 利用自定义事件机制

this.props.function()

跨级组件通信

组件新能优化

影响网页性能最大的因素是浏览器的重绘重排版(回流与重绘?)。

纯函数

纯函数由三大原则构成:

  • 给定相同的输入,返回相同的输出
  • 过程没有副作用(在纯函数中不能改变外部状态)
  • 没有额外的状态依赖(方法内的状态都只在方法的生命周期内存活,即不能再方法内使用共享变量)
PureRender

Pure指的是组件满足纯函数的条件,即组件的渲染是被相同的props和state渲染进而得到相同的结果

Immutable
key
  • 如果每一个子组件是一个数组或迭代器,必须有一个唯一的key prop
  • key用来做Virtual DOM dif
  • 当key相同时,只渲染第一个相同key的项,且会报一个警告

解读React源码

Virtual DOM实际上是在浏览器端用JavaScript实现的一套DOM API,它之于React就好似一个虚拟空间,包括一套Virtual DOM模型、生命周期的维护和管理、性能高效的diff算法和将Virtual DOM展示为原生DOM的path方法。

Virtual DOM模型

一个DOM标签所需的基本元素:

  • 标签名
  • 节点属性,包含样式、属性、事件等
  • 子节点
  • 标识id

Virtual DOM中的节点称为ReactNode,它分为三种类型ReactElement、ReactFragment和ReactText。其中,ReactElement又分为ReactComponentElement和ReactDOMElement。

创建React元素
//createElement只是做了简单的参数修正,返回一个ReactElement实例对象,也就是虚拟元素的实例
ReactElement.createElement = function(type, config, children){

}

DOM标签组件

ReactDOMComponent针对Virtual DOM标签的处理主要分为:

  • 属性的更新,包括更新样式、更新属性、处理事件等。
  • 子节点的更新,包括更新内容、更新子节点,涉及diff算法。
更新属性
  • 如果存在事件,则针对当前的节点添加事件代理
  • 如果存在样式,首先会对样式进行合并操作,然后创建样式
  • 创建属性
  • 创建唯一标识

删除不需要的旧属性,更新新属性。

更新子节点
  • 删除不需要的子节点和内容
  • 更新子节点和内容

生命周期的管理艺术

生命周期在不同状态下的执行顺序:

  • 当首次挂载时,按顺序执行getDefaultPropsgetInitialState、componentWillMount、render、componentDidMount
  • 当卸载组件时,执行componentWillUnmount
  • 当重新挂载组件时,按顺序执行getInitialState、componentWillMount、render和componentDidMount,但并不执行getDefaultProps
  • 当再次渲染组件时,组件接受到更新的状态,此时按顺序执行componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate。
first render
first render
props change
props change
state change
state change

自定义组件的声明周期主要通过3个阶段进行管理:MOUNTING、RECEIVE_PROPS、UNMOUNTING

MOUNTING

mountComponent负责管理生命周期中的getInitialState、componentWillMount、render和componentDidMount。

  • getDefault是通过构造函数进行管理的,所以也是整个生命周期中最先开始执行的,只执行一次
  • 此时在componentWillMount中调用setState方法不会触发re-render,而是会进行state合并
  • mountComponent的本质是通过递归渲染内容,由于递归的特性,父组件的componentWillMount在其子组件的componentWillMount之前调用,父组件的componentDidMount在子组件的componentDidMount之后调用。
RECEIVE_PROPS

updateComponent负责管理生命周期中的componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate。

  • 首先通过updateComponent更新组件,如果前后元素不一致,说明需要进行组件更新
  • 此时在componentWillReceiveProps中调用setState不会触发re-render,而是会进行state合并
  • 只有在render和componentDidUpdate中才能获取到更新后的this.state
  • updateComponent本质上也是通过递归渲染内容,父组件的componentWillUpdate是在其子组件的componentWillUpdate之前调用,父组件的componentDidUpdate子组件的componentDidUpdate之后调用
UNMOUNTING

unmountComponent负责管理生命周期中的componentWilUnmount。

  • 如果存在componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态
  • 此时在componentWillUnmount中调用setState不会触发re-render,因为所有更新队列和更新状态都被重置为null,并清除了公共类,完成了组件卸载操作

解密setState机制

  • setState通过一个队列机制实现state更新
  • 当执行setState时,会将需要更新的state合并后放入状态机,而不会立刻更新this.state
  • 如果在shouldComponentUpdate或componentWillUpdate方法中调用setState,会造成循环调用,使得浏览器内存占满后崩溃
setState调用栈
import React, {component} from 'react';

class Example extends Component {
    constructor() {
        super();
        this.state = {
            val: 0;
        }
    }

    componentDidMount() {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);

        this.setState({val: this.state.val + 1});
        console.log(this.state.val);

        setTimeout(() => {
            this.setState({val: this.state.val + 1});
            console.log(this.state.val);

            this.setState({val: this.state.val + 1});
            console.log(this.state.val);
        }, 0);
    }

    render() {
        return null;
    }
}

//输出 0 0 2 3

事务
  • 事务就是将需要执行的方法使用wrapper封装起来,再通过事务提供的perform方法执行

diff算法

diff帮助计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面

详解diff
diff策略
  • Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  • 对于同一层级的一组子节点,可以通过唯一id进行区分

React分别对tree diff、component diff以及element diff进行算法优化。

tree diff
  • 对树进行分层比较,两棵树只会对同一层次的节点进行比较
  • 当发现节点已经不存在时,则改节点及其子节点会被完全删除掉
component diff
  • 如果是同一类型的组件,按照原策略继续比较Virtual DOM树即可
  • 如果不是,则将改组件判断为dirty component,从而替换整个组件下的所有子节点
  • React允许用户通过shouldComponentUpdate()来判断该组件是否需要进行diff算法分析
element diff
  • INSERT_MARKUP: 新的组件类型不在旧集合里,需对新节点执行插入操作
  • MOVE_EXISTING: 旧集合中有新组件类型,且element是可更新类型,需要做移动操作,可以复用以前的DOM节点
  • REMOVE_NODE: 旧组件类型,在新集合里也有,但对应的element不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作

节点在新集合中的索引值大于在旧集合中的索引时,需移动
删除操作是移动完成之后遍历旧集合,若有新集合中未出现的节点则删除

React Patch方法

将tree diff计算出来的DOM差异队列更新到真实的DOM节点上,让浏览器能够渲染出更新的数据

  • 主要通过遍历差异队列实现

认识Flux架构模式

React独立架构

  • 含有抽象数据而没有业务逻辑的组件为容器型组件
  • 没有数据请求逻辑只有业务逻辑的组件为展示型组件

MV*与Flux

MVC/MVVM

主要涉及三种角色:Model、View和Controller

  • Model:负责保存应用数据,和后端交互同步应用数据,或校验数据
  • View:是Model的可视化表示,表示当前状态的视图。前端View负责构建和维护DOM元素。
  • Controller:负责连接View和Model,Model的任何改变会应用到View中,View的操作会通过Controller应用到Model中
Flux

核心思想是<mark style="box-sizing: border-box;">数据和逻辑永远单向流动</mark>。

flux

在Flux应用中,数据从action到dispatcher再到store,最终到view的路线是单项不可逆的

Flux基本概念

一个Flux应用由3大部分组成:dispatcher、store和view,其中dispatcher负责分发事件;store负责保存数据,同时响应时间并更新数据;view负责订阅store中的数据。

dispatcher

dispatcher是Flux中最核心的方法,也是flux这个npm包中的核心方法。 只需关心.register(callback)和.dispatch(action)这两个API。

  • register方法用来注册一个监听器
  • dispatch方法用来分发一个action
action

action是一个普通的JavaScript对象,一般包含type、payload等字段,用于描述一个事件以及需要改变的相关数据。

store
  • 在Flux中,store负责保存数据,并定义修改数据的逻辑,同时调用dispatcher的register方法将自己注册为一个监听器。
  • 每次使用dispatcher的dispatch方法分发一个action时,store注册的监听器会被调用,同时得到这个action作为参数。
  • 在Flux中,store对外只暴露getter而不暴露setter,即只能读取store中的数据而不能进行任何修改。
controller-view

一般来说,controller-view是整个应用最顶层的view,主要进行store与React组件之间的绑定,定义数据更新以及传递的方式

view

如果界面操作需要修改数据,必须使用dispatcher分发一个action。

actionCreator

深入Redux应用架构

Redux简介

Redux三大原则
单一数据源
  • 在Redux的思想里,一个应用永远只有唯一的数据源。
  • 整个应用状态都保存在一个对象中
状态是只读的

定义一个reducer,其功能是根据当前触发的action对当前应用的状态(state)进行迭代。没有直接修改应用的状态,而是返回了一份全新的状态

  • Reducer提供的createStore方法会根据reducer生成store
  • 用store.dispatch方法来修改状态

状态修改均由纯函数完成 在Redux里,通过定义reducer来确定状态的修改,而每一个reducer都是纯函数,即其没有副作用,接受一定的输入,必定会得到一定的输出。

Redux核心API

Redux的核心是一个store,这个store由Redux提供的createStore(reducers[, initialState])方法生成。

  • 在Redux里,负责响应action并修改数据的角色就是reducer
  • reducer在处理action的同时,还需接受一个previousState参数
  • reducer的职责是根据previousState和action计算出新的newState

Redux中最核心的API是createStore,通过createStore方法创建的store是一个对象,包含四个方法:

  • getState():获取store当前状态
  • dispatch(action):分发一个action,并返回这个action,这是<mark style="box-sizing: border-box;">唯一能改变store中数据的方式</mark>
  • subscribe(listener):注册一个监听者,在store发生变化时调用
  • replaceReducer(nextReducer):更新当前store里的reducer,一般只在开发模式中调用该方法

subscribe()和replaceReducer()方法一般会在Redux与某个系统做桥接的时候使用

与React绑定

react-redux提供了一个组件和一个API帮助Redux和React进行绑定。

一个是React组件<Provider />,一个是connect()

  • <Provider />接受一个store作为props,它是整个Redux应用的顶层组件
  • connect()提供了在整个React应用的任意组件中获取store中数据的功能

Redux middleware

middleware的由来
Redux同步数据流动
redux同步数据流动
应用middleware后Redux处理事件的逻辑
应用middleware后Redux处理事件的逻辑

每一个middleware处理一个相对独立的业务需求,通过串联不同的middleware实现变化多样的功能

理解middleware机制

Redux提供了applyMiddleware方法来加载middleware。

import compose from './compose';

export default function applyMiddleware(...middlewares) {
    return (next) => (reducer, initialState) {
        let store = next(reducer, initialState);
        let dispatch = sotre.dispatch;
        let chain = [];

        var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action),
        };

        chain = middlewares.map(middleware => middleware(middlewareAPI));
        dispatch = compose(...chain)(store.dispatch);

        return {
            ...store,
            dispatch,
        };
    }
}

函数式编程思想设计

middleware是一个层层包裹的匿名函数,即函数式编程中的currying,是一种使用匿名单参数函数来实现多参数的方法。

currying的middleware皆有的好处:

  • 易串联:currying函数具有延迟执行的特性,通过不断currying形成的middleware可以累积参数,再配合组合(compose)的方式,很容易形成pipeline来处理数据流
  • 共享store:在applyMiddleware执行的过程中,store还是旧的,但是因为闭包的存在,applyMiddleware完成后,所有的middleware内部拿到的store是最新且相同的。
给middleware分发store
//创建普通的store
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);

组合串联middleware
dispatch = compose(...chain)(store.dispatch);

//假设n=3,dispatch为
dispatch = f1(f2(f3(store.dispatch)));

不能在middleware中调用dispatch

Redux异步流

Redux与路由

在Redux应用中,遇到了一些新的问题,其中最迫切的是,应用程序的所有状态都应该保存在一个单一的store中,而当前的路由状态很明显也属于应用状态的一部分。如果直接使用React Router,就意味着所有路由相关的信息脱离了Redux store的控制,这样就违背了Redux的设计思想。

React Router
路由的基本原理

理由的基本原理即是保证View和URL同步,而View可以看成是资源的一种表现。

路由的基本原理
React Router特性
  • 在React中,组件就是一个方法,props作为方法的参数,当它们发生变化时触发方法执行,重绘View
  • 在React Router中,可以把Router组件看成一个方法,location作为参数,返回的结果同样是View
声明式路由
  • React是声明式编程,所有的交互逻辑都在render返回的JSX标签中得到体现
  • React Router允许使用JSX表现来书写声明式的路由
import {Router, Route, browserHistory} from 'react-router';

const routes = (
    <Router history={browserHistory}>
        <Route path="/" component={App} />
    </Router>
);

嵌套路由及路径匹配
import {Router, Route, IndexRoute, browserHistory} from 'react-router';

const routes = (
    <Router history={browserHistory}>
        <IndexRoute component={MailList} />
        <Route path="/mail/:mailId" component={Mail}></Route>
    </Route>
);

  • 在声明路由时,path属性指明了当前路由匹配的路径形式
  • 若某条路由需要参数,只用加上 :参数名 即可
支持多种路由切换方式

路由切换可以使用hashChange或history.pushState。

  • hashChange拥有良好的浏览器兼容性,但是url中多了/#/部分
  • history.pushState能提供优雅的url,但需要额外的服务端配置解决任意路径刷新的问题

React Router提供了两种解决方案

<mark style="box-sizing: border-box;">browserHistory即history.pushState的实现</mark>

React Router Redux

职责主要是将应用的路由信息与Redux的store绑定在一起

采用Redux架构时,所有的应用状态都必须放在一个单一的store中管理,路由状态也不例外

将React Router与Redux store绑定

React Router Redux提供了简单直白的API syncHistoryWithStore来完成与Redux store的绑定工作。只需传入React Router中的history,以及Redux中的store,就可以获得一个增强后的history对象。

import { browserHistory } from  'react-router';
import { syncHistoryWithState } from 'react-router-redux';
import reducers from '<project-path>/reducers'

const store = createStore(reducers);
const history = syncHistoryWithStore(browserHistory, store);

用Redux的方式改变路由

无论是Flux还是Redux,想要改变数据,必须要分发一个action

  • 在此之前,需要对Redux的store进行一些增强,以便分发的action能被正确识别
import { browserHistory } from 'react-router';
import { routerMiddleware} from 'react-router-redux';

const middleware = routerMiddleware(browserHistory);
const store = createStore(
    reducers,
    applyMiddleware(middleware)
);

  • 用store.dispatch来分发一个路由变动的action
import {push} from 'react-router-redux';

store.dispatch(push('/home'));

Redux与组件

容器型组件

容器型组件,意为组件是怎么工作的,具体一些就是数据是怎么更新的。不包含任何Virtual DOM的修改或组合,也不会包含组件的样式。

  • 如果映射到Flux上,容器型组件就是与store绑定的组件
  • 如果映射到Redux上,容器型组件就是使用connect的组件
展示型组件

展示型组件,意为组件是怎么渲染的。包含Virtual DOM的修改和组合,也可能包含组件的样式。

Redux中的组件
Layouts
  • 指的是页面布局组件,描述了页面的基本结构,目的是将主框架与页面主题内容分离
  • 常常是无状态函数,传入主题内容的children属性
//一般写法为
const layout = ({ children }) => (
    <div className='container'>
        <Header />
        <div className='content'>
            { children }
        </div>
    </div>
);

views
  • 指的是子路由入口组件,描述子路由入口的基本结构,包含由此路由下所有的展示型组件
  • 为了保持子组件的纯净,在这一层组件中定义了数据和action的入口,从这里开始将它们分发到子组件中去
Components
  • 末级渲染组件,描述了从路由以下的子组件
  • 包含具体的业务逻辑和交互
  • 所有的数据和action都是由Views传下来的,即其是可以完全脱离数据层而存在的展示型组件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容