React - why & basic use
ChenYang Lv1

今天我们来聊聊为什么要选择React

1. 为什么 React

React 是 Facebook 2011 开发的,是 JavaScript MVC 框架下的应用,MVC 将应用分为三个部分:数据层,视图层和控制层。React 主要用于构建 UI,属于视图层。

image.png

“Learn React Once and Write Everywhere” - Reactjs.org

  • React 灵活的用于组件化开发,你可以把一个按钮 / 表单等等抽成一个组件。 React 既可以编写 Web 应用,也可以使用 React Native 开发 apps。
  • 相比于其他的框架,react 开发更简单。通过函数式编程结合 JSX 语法,可以极大减少代码量。下面对比了 Angular, Vue.js 和 React 在代码循环的写法,可以看到 react 写法更接近原生的 JS。

Angular Loop
Vue.js Loop
image.png

  • React 有 Facebook 支持和广泛的社区支持

React GitHub 已有 164K 的 star,是 GitHub Top5 的仓库。

image.png

React 的 NPM 包每周也有数百万次下载。

image.png

  • React 性能优化。React 开发团队意识到 JS 本身很快,但是更新 DOM 会导致 JS 变慢,因此 React 使用最高效和最智能的方式优化 DOM 更改次数。主要通过虚拟 DOM 和 conCurrent mode 来实现,见 2.1 和 2.7。

1.1 MVC & MVP & MVVM

MVC, MVP 和 MVVM 是软件架构设计模式,是解决某一类问题而抽象出来的方法。

image.png

1.2 React 相比于原生 JS

  • 模块化开发
  • 不用写原生 DOM 操作,通过虚拟 DOM 优化性能
  • UI = f(data) 函数式编程

1.3 单向数据流

自顶向下,单向数据流是 react 的特点之一,state 只能向下传递。

1.4 React 代数效应

代数效应与 React - 知乎

2. React 使用

2.1 虚拟 DOM & diff & fiber

为什么需要虚拟 DOM:

  • 提升性能,减少重绘重排。前端性能优化的一个秘诀就是尽可能减少 DOM 操作,频繁的 DOM 操作会造成浏览器的重排和重绘。
  • 提高开发效率,无须手动操作 DOM。手动操作 DOM 无法保证程序性能,容易写出性能低的代码,省略手动 DOM 操作可以提高开发效率。
  • 实现跨平台。比如 node.js,如果想实现 SSR,借助虚拟 DOM。
  • 跨浏览器兼容。React 基于 VitrualDom 自己实现了一套自己的事件机制,自己模拟了事件冒泡和捕获的过程,采用了事件代理,批量更新等方法,抹平了各个浏览器的事件兼容性问题。

虚拟 DOM 原理:

  • 虚拟 DOM 是真实 DOM 的映射,是一种树形结构,可以理解为真实 DOM 和 JS 之间的缓存,保存了真实 DOM 一些基本属性和关系。
  • diffing 算法。react 会维护两颗虚拟 DOM 树,一颗保存当前状态,另外一颗渲染更新后状态,通过比较两棵树的差异,进行 DOM 修改,比较的方法就是 diffing 算法。React diffing 核心思想是增量式渲染。通过对比新的列表中的节点,在原本的列表中的位置是否是递增,来判断当前节点是否需要移动,将时间复杂度优化到 O(n)。diffing 算法通过 key 来进行比较,因此 key 需要唯一,不建议使用下标作为 key。
  • fiber(协程)是 React16 中的新的协调引擎,目的是使得虚拟 DOM 可以增量式渲染。核心思想是,适时的让出 cpu 执行权,执行更高权限的事件,让用户不感觉卡顿。结合 concurrent mode (requestIdleCallback 和 requestAnimationFrame) 来实现 cpu 调度。走进 React Fiber 的世界
  • React Concurrent。 并发模式,包括调度器(Scheduler), 协调器(Reconciler), 渲染器(Renderer),通过 requestIdleCallBack(在浏览器空闲帧的时候执行), requsetAnimationFrame(在每一帧都执行)来优化 react 性能。

2.2 JSX

JSX 是是 JavaScript XML,React 提供的 JS 语法糖。实际上引入 React 后可以再 JS 中写 HTML。JSX 将 XML 语法加入到 JavaScript 中,在 JS 中写了 JSX 将会被预处理成 React Element

  • JSX 中可以写常规 HTML,可以通过{props}往 html 中插入变量 JS 表达式,或者带参数的函数{func(props)}。
  • JSX 编译后,是一个函数调用,返回值为 JS 对象,JSX 可以作为表达式,如 IF 判断。
  • 可以再标签中添加属性,属性若为字符串,则加上引号,若为对象或表达式,加上{},属性 key 使用驼峰命名(JSX 中 className,没有 class)
  • 可以单闭合 <img/>
  • 可以给 html 添加类但 class 需改写成 className
    为什么 React 要创建 JSX: 渲染的逻辑处理与 UI 逻辑其实是耦合的, event, state, data 互相关联,既然如此,那么就把 html 标记语言与逻辑处理相关的 js 内容放在一起,组成一个松耦合的模块,这个模块就是 JSX 元素。
    JSX 使用: 首先怎么才能写 JSX 呢,在普通的 JS 文件中需引入 react,reactDOM(若要对 DOM 进行操作)以及 babel 或者通过 Babel 在线编译
    JSX 防范 XSS 攻击: XSS 是跨站脚本注入攻击。
    • 由于当你尝试通过{html}进行插入 html 代码时, React 会自动将 html 转为字符串,故 React 可部分防止 XSS 攻击
    • JSX 中是通过传入函数作为事件处理方式,而不是传入字符串,字符串可能包含恶意代码
    • JSX 无法抵御 XSS 攻击: 如:<a href="{...}" />, <img src={...} />, <iframe src="{...} />,css注入style={...} prop,,或者通过设置 a 的 href 为 javascript:xxx,以及使用 base64 编码的数据进行替换,又或从用户处接受了被恶意控制的 props。

2.3 组件通信

  • 父子: 父组件通过 props 传递,子组件通过父组件传递回调改变自身一些状态(隐藏)。
  • 跨级: 层层传递 props(麻烦) / context(污染组件) / redux。
  • 没有关系的组件: redux / 发布订阅模式。

2.4 setState 同步 & 异步

从 API 的角度来看应该是同步的,但 React 自己不保证 setState 之后能够立即拿到改变后的结果。
setState 在 React 能够控制的范围被调用,它就是异步的。比如合成事件处理函数, 生命周期函数, 此时会进行批量更新, 也就是将状态合并后再进行 DOM 更新。
如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。比如原生事件处理函数中, 定时器回调函数中, Ajax 回调函数中, 此时 setState 被调用后会立即更新 DOM 。
因为异步实际上是对性能的优化。unstable_batchedUpdates 可以实现强制的批量更新。

2.5 生命周期

  • componentWillMount() 渲染前调用,只触发一次
  • componentDidMount() 渲染后调用,此时 DOM 已经生成,通过 this.getDOMNode()访问。
  • componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。
  • shouldComponentUpdate 返回 Boolean,在组件接收到新的props或者state时被调用。
  • componentWillUpdate 在组件接收到新的props或者state但还没有render时被调用。
  • componentDidUpdate 完成更新后调用。
  • componentWillUnMount 组件卸载时调用。
  • getDerivedStateFromError

2.6 React Hooks

函数式组件,React16.8 新增特性,目的是提高组件的复用,解决类式组件组件复杂性,增加符合函数式编程中的代数效应,用于将副作用从函数中分离。

常用 hooks

  • useState,返回一个数组,第一参数为 state,第二个参数为 setState。不能在 if / for 里使用,会导致 state 被覆盖。因为 React 使用链表保存 useState 值
1
const [state, setState] = useState(initialState)
  • useEffect,集合 componentDidMount,componentDidUpdate,componentWillUnMount 生命周期。接收一个数组作为依赖项,在依赖项改变时调用 fn,如果不传,则在每次 componentDidUpdate 会触发,如果传空数组,componentDidMount 触发。DOM 更新后触发
1
useEffect(fn, [])
  • useLayoutEffect,类似于 useEffect,同步执行,会阻塞页面渲染。
  • useContext,它是以 Hook 的方式使用 React Context。
1
const context = useContext(Context)
  • useReducer,语法糖跟 reudx 差不多
1
const [state, dispatch] = useReducer(reducer, initialArg, init)
  • useRef
  • useMemo, 常用的性能优化 hook, 返回缓存变量,减少不必要渲染,类似于 shouldComponentUpdate,DOM 更新前触发。
1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • useCallback,常用的性能优化 hook, 返回缓存函数,减少不必要渲染,类似于 shouldComponentUpdate
1
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
  • 自定义 hook,通过 use 开头,有变量和 set 方法。

2.7 Redux

redux 是 React 的一个全局状态管理库,简化 react 中的单向数据流。

  • action: JSON 对象,type 和 payload 键
  • dispatch: 分发
  • reducer: 纯函数,将当前 state 和 action 作为参数,返回新的 state。
    image.png

2.7 提高性能

  • 适当使用 shouldComponentUpdate,减少组件渲染。
  • hooks 中 useMemo,useCallBack。
  • 循环时使用唯一的 key。
  • 使用 Map 替代数组。map.get()和 map.has()比数组查找效率高。
  • useWhyDidYouUpdate 调试组件渲染的原因

引用

Why You Should Use React.js For Web Development

MVC, MVP and MVVM Design Pattern

React、Vue2、Vue3 的三种 Diff 算法

React fiber

# React Concurrent 模式抢先预览上篇: Suspense the world