为什么要用到函数式组件?

react官网:

class难以理解:

  • Javascript的this指针
  • 官方进行组件预编译优化,class常常让优化措施无效

理解React?

  • React框架? 不同于angular,react官网并不称自己为框架:用于构建用户界面的JavaScript库

路由,状态管理,并不是react团队开发的,而是交给社区做,所以它并不完全能叫一个框架,只是一个库。

  • react使用者上限和下限差距极大,会使用很简单,成为高手比较难

React优势

组件化,高内聚低耦合,但并没有提供一整套解决方案,需要依靠社区提供的集成方案

JSX代码可读性好

为什么React选择JSX?其他方案为什么不好

JSX本质

官网:实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

被编译为:

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

接着会产生疑问:React.createElement()到底是一个什么样的方法?

React.createElement(
  type, //第一个参数为html标签名称
  [props], //第二个参数为属性,如className
  [...children] //第三个参数为子结点
)

在react文件中,第一句常常是:

import React from 'react';

但是程序中却没有出现显式的对引入的React的调用,删了这一句也不行

查:

在渲染Dom的时候调了React.createElement()

在https://babeljs.io中可以看到该过程:

image-20211110234827340

看createElement()源码:

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 * type: 节点类型
 * 如果是原生的节点类型,那么就是一个字符串 tagName
 * 如果是一个组件(我们自己定义的、或者React内置提供的)那么就是一个变量
 *
 * config: 就是写在jsx上的属性,它们都会转为key: value格式存到config对象上
 *
 * children: 所有后代元素
 */
 export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  // 对于组件内部来说传入的属性就是props
  const props = {};

  // 预定义key ref等变量用于后面存值
  // key ref属于特殊的attrs需要剔除
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    if (hasValidRef(config)) {
      // 存储合法的ref
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      // 存储合法的key => string
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 其他属性枚举到props对象
    for (propName in config) {
      if (
        // 仅限config自身的属性 && 排除React预留的props
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
   // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  // 获取children的个数,一个节点下的children是可以有多个的
  const childrenLength = arguments.length - 2;

  // 赋值处理props.children
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    // 定长数组、再遍历拷贝
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  // 处理defaultProps,另外需要处理defaultProps的顺序
  /**
   * class CustomButton extends React.component{}
   * CustomButton.defaultProps = {}
   * 可见type是一个组件
   */
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      // 如果 props.color 被设置为 null,则它将保持为 null
      // 关于这里为什么是undefined、而不是null,其实很好理解,定义了一个遍历而不初始化那它就是undefined,而null需要手动初始化。
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

 if (__DEV__) {
    if (key || ref) {
      // 处理displayName,即推导过程
      // 如果是组件、先从组件的displayName取、其次是组件的名字、最后是默认值
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }

  // 返回一个真正的ReactElement
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

/**
 * Factory method to create a new React element. This no longer adheres to
 * the class pattern, so do not use new to call it. Also, no instanceof check
 * will work. Instead test $$typeof field against Symbol.for('react.element') to check
 * if something is a React Element.
 *
 * @param {*} type
 * @param {*} props
 * @param {*} key
 * @param {string|object} ref
 * @param {*} owner
 * @param {*} self A *temporary* helper to detect places where `this` is
 * different from the `owner` when React.createElement is called, so that we
 * can warn. We want to get rid of owner and replace string `ref`s with arrow
 * functions, and as long as `this` and owner are the same, there will be no
 * change in behavior.
 * @param {*} source An annotation object (added by a transpiler or otherwise)
 * indicating filename, line number, and/or other information.
 * @internal
 */
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    // 通过Symbol作为独一无二标识符 Symbol.for进行复用
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    // 节点类型
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  if (__DEV__) {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

为什么选择JSX**?**

JSX 语法糖允许前端开发者使用我们最为熟悉的类 HTML 标签语法来创建虚拟 DOM,在降低学习成本的同时,也提升了研发效率与研发体验

React高性能体现:虚拟DOM

(参考资料:https://www.bilibili.com/video/BV19p4y1z7LK?p=9&spm_id_from=pageDriver)

Web开发需要将变化的数据实时反映到UI上,需要我们频繁地操作DOM,这就是性能瓶颈的原因(如何进行高性能的复杂DOM操作?)

React:引入Virtual DOM。在浏览器端用Javascript实现了一套DOM API。基于react开发的所有DOM构造都通过虚拟DOM进行。

当数据变化 ,react重构DOM树,将当前树和上一次树进行对比,得到DOM的区别,仅仅更新变化的部分。

**(精髓)**存在一个Event loop,在一个loop中两次数据变化会被合并,A->B->A在一个loop中发生,UI不发生任何变化。react认为不变,所以不会更新到B,再更新到A。

频繁更新虚拟DOM,性能不会差?

虚拟DOM是内存数据,性能高,对实际DOM进行操作的仅仅是Diff部分。

React Fiber

不是上面的Event Loop。

是React 16之后发布的一种核心算法。官网:是对核心算法的重新实现。之前用的是diff算法。

为了解决的问题:之前的react,更新过程是同步的,可能导致性能问题。

React决定加载或更新组件时,会干很多事,比如:调用各个组件生命周期,计算比较虚拟DOM树,更新DOM树。这个过程是同步进行的,一次加载/更新开始,中间不会中断。因为JS是单线程,组件树很大时,每个同步任务耗时太长,出现加载卡顿。

React Fiber:分片。把耗时的任务分成若干片。每一片运行时间很短。每一小片执行完成之后,都会给其他任务机会去执行,从而避免进程被独占。(很像CPU的时间片,把cpu资源类比于js单线程,把线程类比于react中的很多任务)

image-20211118172121553

生命周期

16.3之前

image-20211119103202357

现在的生命周期:

image-20211119113223593

getDrivedStateFromProps(): 根据props生成state

官网不推荐使用,容易写出bug

Redux

Flux: Redux鼻祖

image-20211120191207422

  • View:视图层
  • ActionCreator :视图层发出的消息
  • Dispatcher: 用来接受Action,执行回调函数
  • Store(数据层):用来存放应用的状态,一旦发生变动,提醒Views更新界面

但是Flux实现太复杂

需要Redux的项目

  • 用户使用的方式复杂
  • 不同身份用户有不同的使用方式
  • 多个用户之间可以协作
  • 与服务端大量交互,或者使用了websocket
  • View需要从多个来源获取数据

从组件层面考虑:

  • 某个组件的状态需要共享
  • 某个状态在任何地方都可拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

设计思想:

1、Web应用是一个状态机,数据和视图一一对应

2、全局状态保存在一个对象中