今天我们来聊聊为什么要选择React
1. 为什么 React
React 是 Facebook 2011 开发的,是 JavaScript MVC 框架下的应用,MVC 将应用分为三个部分:数据层,视图层和控制层。React 主要用于构建 UI,属于视图层。
“Learn React Once and Write Everywhere” - Reactjs.org
- React 灵活的用于组件化开发,你可以把一个按钮 / 表单等等抽成一个组件。 React 既可以编写 Web 应用,也可以使用 React Native 开发 apps。
- 相比于其他的框架,react 开发更简单。通过函数式编程结合 JSX 语法,可以极大减少代码量。下面对比了 Angular, Vue.js 和 React 在代码循环的写法,可以看到 react 写法更接近原生的 JS。
- React 有 Facebook 支持和广泛的社区支持
React GitHub 已有 164K 的 star,是 GitHub Top5 的仓库。
React 的 NPM 包每周也有数百万次下载。
- React 性能优化。React 开发团队意识到 JS 本身很快,但是更新 DOM 会导致 JS 变慢,因此 React 使用最高效和最智能的方式优化 DOM 更改次数。主要通过虚拟 DOM 和 conCurrent mode 来实现,见 2.1 和 2.7。
1.1 MVC & MVP & MVVM
MVC, MVP 和 MVVM 是软件架构设计模式,是解决某一类问题而抽象出来的方法。
1.2 React 相比于原生 JS
- 模块化开发
- 不用写原生 DOM 操作,通过虚拟 DOM 优化性能
- UI = f(data) 函数式编程
1.3 单向数据流
自顶向下,单向数据流是 react 的特点之一,state 只能向下传递。
1.4 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。
2.7 提高性能
- 适当使用 shouldComponentUpdate,减少组件渲染。
- hooks 中 useMemo,useCallBack。
- 循环时使用唯一的 key。
- 使用 Map 替代数组。map.get()和 map.has()比数组查找效率高。
- useWhyDidYouUpdate 调试组件渲染的原因
引用
Why You Should Use React.js For Web Development