React基础

  1. 所谓组件,就是能完成某个特定功能的、独立的、可重用的代码
  2. 组件的划分要满足高内聚(逻辑紧密相关的内容放在同一组件中)和低耦合(不同组建之间依赖关系要弱化)
  3. Component是所有组件的基类
  4. React判断一个元素是HTML元素还是React组件的原则,就是看第一个字母是否大写
  5. React的理念:UI = render(data),React应用通过重复渲染来实现用户交互
  6. Virtual DOM
    I. 利用Virtual DOM,让每次渲染都只重新渲染最少的DOM元素
    II. 前端性能优化的其中一个原则就是尽量减少DOM操作,因为它会引起浏览器对网页的重新布局
  • 在React中,render执行的结果得到的并不是真正的DOM节点,而仅仅是JS对象,称之为虚拟DOM
  • 虚拟DOM具有高效的diff算法,随时刷新整个页面,从而确保只对界面上真正变化的部分进行实际的DOM操作
  1. JSX与HTML区别
import React, { Component } from 'react';
class ClickCounter extends Component {
    constructor(props) {
        super(props);
        this.onClickButton = this.onClickButton.bind(this);
        this.state = {count: 0};
    }
    
    onClickButton() {
        this.setState({count: this.state.count + 1});
    }
    
    render() {
        return (
            <div>
                <button onClick={this.onClickButton}>Click Me</button>
                <div>
                    Click Count: {this.state.count}
                </div>
            </div>
        );
    }
}
export default ClickCounter;
复制代码

HTML:

  1. onclick添加的事件处理函数实在全局环境下执行,污染全局环境
  2. 给很多DOM元素添加onclick事件会影响网页性能
  3. 当使用onclick的DOM元素从DOM树中动态删除的话,需要把对应的事件处理器注销,否则会造成内存泄露

JSX:

  1. onClick挂在的每个函数都可以控制在组件范围内,不会污染全局环境
  2. 使用了事件委托的方式处理点击事件,无论多少onClick出现,最后都只在DOM树上添加一个事件处理函数,挂在最顶层DOM节点上
  3. React在组件生命周期的卸载过程中,会清楚相关的所有事件处理函数,使得内存泄漏不再是问题

什么是事件委托?

  1. 在父元素上绑定事件,监听子元素的冒泡事件,并获取到事件发生的子元素
  2. 事件委托可以避免对每个节点添加事件监听器,减少DOM结点的操作次数,提高性能

Diff算法:

  1. 传统的diff算法通过循环递归对节点进行依次比较,算法复杂度为O(n^3),其中n是树中节点的个数
  2. diff算法的三个策略
  • DOM节点跨层级的移动操作发生频率很低,是次要矛盾;
// Tree Diff
React对Virtual DOM树进行层级控制,只会对相同层级的DOM节点进行比较。
即:同一个父元素下的所有子节点,当发现节点已经不存在了,则会删除掉该节点下所有的子节点,不会再进行比较。
复制代码
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构,这里也是抓前者放后者的思想;
// Component Diff
如果是同一个类型的组件,则按照原策略进行Virtual DOM比较。
如果不是同一类型的组件,则将其判断为dirty component,从而替换整个组价下的所有子节点。
如果是同一个类型的组件,有可能经过一轮Virtual DOM比较下来,并没有发生变化。
如果我们能够提前确切知道这一点,那么就可以省下大量的diff运算时间。因此,React允许用户通过shouldComponentUpdate()来判断该组件是否需要进行diff算法分析。
复制代码
例如:如果我们把<Header>替换为<ExampleBlock>,React将会移除header接着创建example block。
复制代码
  • 对于同一层级的一组子节点,通过唯一id进行区分,即没事就warn的key。

React组件的数据

1. prop

a) prop是组件的对外接口,是从外部传递给组件的数据
b) React组件通过定义自己能接受的prop,就定义了自己的对外公共接口

<SampleButton
    id="sample" borderWidth={2} onClick={onButtonClick} style={{color:"red"}}
/>
复制代码

其中,
i. id、borderWidth、onClick和style都是prop
ii. 当prop的类型不是字符串类型时,在JSX中必须用花括号{}把prop值包住
iii. 函数类型的prop相当于让父组件交给子组件一个回调函数
,子组件调用该函数类型的prop时,可以带上必要的参数,这样就可以反过来把信息传递给父组件

class Counter extends Component {
    constructor(props) {
        super(props);
        
        this.onClickIncrementBtn = this.onClickIncrementBtn.bind(this);
        this.onClickDecrementBtn = this.onClickDecrementBtn.bind(this);
        
        this.state = {
            count: props.initValue || 0;
        }
    }
    
    render(){
        const { caption } = this.props;
        
        return (
            <div>
                <button onClick={this.onClickIncrementBtn}>+</button>
                <button onClick={this.onClickDecrementBtn}>-</button>
                <span>{caption} count: {this.state.count}</span>
            </div>
        ); 
    }
}
复制代码

React的propTypes可以用来设定该组件支持哪些prop,以及每个prop应该是什么格式

Counter.propTypes = {
    caption: PropTypes.string.isRequired,
    initValue: PropTypes.number
}
复制代码

propTypes检查只是一个辅助开发的功能,所以在生产代码中应该将其去掉,可以考虑使用babel-react-optimize

2. state

  1. state是一个JS对象,通过this.state读取组件的当前state,通过this.setState修改组件state
  2. this.setState()首先是改变lthis.state的值,然后还驱动组件经历更新过程,从而让this.state里新的值出现在界面上
prop和state的对比:
- prop用定义外部接口,state用于记录内部状态
- prop的赋值在外部世界使用组件时,state的赋值在组件内部
- 组件不应该改变prop值,而state存在的目的就是让组件来修改
复制代码

3. refs

  • React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。
  • 这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例。
class MyComponent extends React.Component {
  handleClick() {
    // 使用原生的 DOM API 获取焦点
    this.refs.myInput.focus();
  }
  render() {
    //  当组件插入到 DOM 后,ref 属性添加一个组件的引用于到 this.refs
    return (
      <div>
        <input type="text" ref="myInput" />
        <input
          type="button"
          value="点我输入框获取焦点"
          onClick={this.handleClick.bind(this)}
        />
      </div>
    );
  }
}

// 也可以使用 getDOMNode()方法或React.findDOMNode()方法获取DOM元素
var inputDOM = this.refs.myInput.getDOMNode();
var inputDOM = React.findDOMNode(this.refs.myInput);

复制代码

4. 组件的生命周期(重点)

装载过程:组件第一次在DOM树中渲染的过程;
更新过程:组件被重新渲染的过程;
卸载过程:组件从DOM中删除的过程;

装载过程(Mount)

  1. constructor
  • 初始化state
  • 绑定成员函数的this环境
  • ES6中,类的每个成员函数在之行时dethis并不是和类实例自动绑定的,而在构造函数中this就是当前组件实例
  1. getInitialState和getDefaultProps
  • 只有用React.createClass方法来创造的组件类才会发生作用,已经被废弃
  1. render
  • 返回一个JSX描述的结构,并不往DOM树上渲染或装载内容,最终由React来操作渲染过程
  • 若返回一个null或false,相当于告诉React,这个组件这次不需要渲染任何DOM元素
  • 它是一个纯函数,完全根据this.state和this.props来决定返回的结果,不应该引起任何状态的改变
  1. componentWillMount
  • 在render函数之前调用(紧贴render),在其中所做的事情均可放在constructor中
  1. componentDidMount
  • 在render函数之后调用(非紧贴render),由于render函数仅仅返回一个JSX描述的结构,并不往DOM树上渲染或装载内容,因此只有React调用各个组件的render函数之后,才会完成装载
  • 在该函数调用的时候,组件已经被装载到DOM树上,因此可以放心使用任何DOM
  • AJAX请求应该在此阶段调用

更新过程(Update)

  1. componentWillReceiveProps(nextProps)
  • 只要父组件的render函数被调用,子组件就会经历更新过程,无论父组件传给子组件的props是否发生改变,都会触发子组件的componentWillReceiveProps函数
  • this.setState方法触发的更新过程不会调用这个函数
  • 由于componentWillReceiveProps并不是当props值发生变化时才被调用,所以有必要将传入参数nextProps和this.props(上次渲染时的props值)做对比,若不同则需要调用this.setState更新内部状态
  1. shouldComponentUpdate(nextProps, nextState)
  • 它决定了一个组件什么时候不需要渲染
  • 它和render是React生命周期中仅有的两个需要有return的函数
  • 只有当传入的props值或state值相比于this.props或this.state发生改变时,才返回true,从而调用render函数
  • 通过this.setState引发的更新过程并不是立即更新组件的state值,在执行到函数shouldComponentUpdate的时候,this.state值依然是执行setState函数之前的值
shouldComponentUpdate(nextProps, nextState) {
    return (nextProps.caption !== this.props.caption) 
        || 
        (nextState.count !== this.state.count);
}
复制代码
  1. componentWillUpdate和componentDidUpdate
  • 当shouldComponentUpdate函数返回true时,React会依次调用componentWillUpdate、render和componentDidUpdate

补充: componentWillUpdate和componentShouldUpdate均不可使用setState,该操作会触发componentUpdate,会造成循环调用

卸载过程

  1. componentWillUnmount
  • 当React组件要从DOM树上删除掉之前,该函数会被调用,去执行一些请理性的工作
  • 例如:在componentDidMount中用非React方法创造了一些DOM元素,有可能会造成内存泄漏

React单向数据流与组件通信

单向数据流

React是单向数据流,从父节点传递到子节点(通过props)。如果顶层的某个props改变了,React会重渲染所有的子节点(未做性能优化)。严格意义上React只提供,也强烈建议使用这种数据交流方式。

父子通信

  1. 父组件更新组件状态 -----props-----> 子组件更新
  2. 子组件更新父组件状态 -----需要父组件传递回调函数-----> 子组件调用触发
  • 父组件通过props传递一个回调函数到子组件中,这个回调函数可以更新父组件,子组件就是通过触发这个回调函数,从而使父组件得到更新。
// 父组件
class Box extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            text: ''
        }
    }
    
    refreshBox() {
        this.setState({
            text: '子组件更新父组件成功'
        });
    }
    
    render() {
        var data = this.props.data;
        var text = this.state.text;
        return (
            <div>
                <div data={data}></div>
                <Son text={this.state.text}
                    refreshBox={this.refreshBox.bind(this)}
                />
            </div>
        );
    }
}

// 子组件
class Son extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return(
            <div>
                {this.props.text}
                <button onClick={this.props.refreshBox}>更新父组件</button>
            </div>
        )
    }
}
复制代码

兄弟组件

  1. 方式一:借助父组件进行传递,通过父组件回调函数改变兄弟组件的props(只适用于组件层次少的)
  2. 方式二:React官方给我们提供了一种上下文方式,可以让子组件直接访问祖先的数据或函数,无需从祖先组件一层层地传递数据到子组件中
  • Parent.childContextType就是这样一个上下文声明,子组件调用祖先组件的方法时,通过 this.context.[callback] 这样就可以进行祖先与子组件间的沟通了。
class Brother1 extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  render(){
    return (
      <div>
        <button onClick={this.context.refresh}>
            更新兄弟组件
        </button>
      </div>
    )
  }
}
Brother1.contextTypes = {
  refresh: React.PropTypes.any
}

class Brother2 extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  render(){
    return (
      <div>
         {this.context.text || "兄弟组件未更新"}
      </div>
    )
  }
}
Brother2.contextTypes = {
  text: React.PropTypes.any
}

class Parent extends React.Component{
  constructor(props){
    super(props);
    this.state = {}
  }
  
  getChildContext(){
    return {
      refresh: this.refresh(),
          text: this.state.text,
      }
    }
  
  refresh(){
    return (e)=>{
      this.setState({
        text: "兄弟组件沟通成功",
      })
    }
  }
  render(){
    return (
      <div>
        <h2>兄弟组件沟通</h2>
        <Brother1 />
        <Brother2 text={this.state.text}/>
      </div>
    )
  }
}
Parent.childContextTypes = {
  refresh: React.PropTypes.any,
  text: React.PropTypes.any,
}
复制代码

MVVM (Model - ViewModel - View,数据驱动视图)

双向绑定

  1. 双向

正向:Data ---> View
反向:View ---> Data

  • 通过模板将数据渲染到页面,模板工具(正向)
  • 页面内容改变后映射到保存的数据中,绑定事件onchange(反向)
  1. 绑定
  • 自动化处理
  1. 原理实现



Object.defineProperty(obj, prop, descriptor);

Descriptor:
1. configurable
2. enumerable
3. writable
4. value

5. get
6. set
- set中会在赋值时,检测两次的值是否相同
复制代码

设计模式



  1. 定义原生data
  2. Observer监听data发生变化,通知观察者列表(Dep)
  • Observer的背后其实就是Object.defineProperty,数据的改变会触发set函数
  1. 观察者列表(Dep)中的成员要调用Watcher中的回调函数,继而更新View
  2. Watcher要负责添加观察者列表成员(Dep)

MVC和MVVM的区别

  • MVVM,数据驱动视图,核心是VM ,常用的有 vue,react MVC的话就是传统的 Model - view - controller 三部分组成
  • MVVM中的View 和 ViewModel可以互相通信。也就是可以互相调用。 MVC中的是单向通信。也就是View跟Model,必须通过Controller来承上启下