React
React 源码
react 为什么实用 JSX?
萝卜青菜各有所爱。
但是 react 团队认为模板方案不好:
- 模板分离了技术栈,增加了技术点。
- 看起来像 html 的 jsx。
- react 规矩性太强,需要 jsx 来辅助
例如使用 vue 上面是 html 模板,下面是js逻辑从而形成一个组件功能。但是 react 将 html 和运行逻辑都运行在一个 class。
JSX 映射虚拟 DOM 的原理?
JSX 是 creatElement 的语法糖,使用的是 babel 的转义器,实际是通过两个方法完成,一个是 creatElement,一个 reactElement
// 定义一个 react 对象
reactElement(type, key, ref, self, source, ReactCurrentOwner.current, props)
Rreact 数据流管理
数据驱动视图?UI = render(data)
data 与 state 的区别是什么?
状态(state)与数据(data),组件中的通信就是 data 的通信,react 核心就是对数据的管理
数据流动:
- 父 -> 子
- 子 -> 父
- 兄弟
- 无关系
单向数据流
MVC的劣势双向数据流。引起混乱。
Action -> Dispatcher -> Store -> View
|-------<------Action<-|
ACTION 视图层发送的消息
redux 是 js 的状态容器
Action -> Reducer -> Store[state, state]
|-------<------View--<-|
redux 优缺点
缺点:
- 使用流程复杂。
- state 不会随着组件销毁,状态残留。
- 频繁更新 store 时,会更加卡顿。
- 不支持 ts。
mobx
原理是利用 es6 proxy 数据劫持,不会想 redux 使用复杂。
React Fiber
超过 16 毫秒后会掉帧。
原来的 React 是通过利用js的执行栈,一直执行到栈空位置。新版的React 维护了自己的执行栈,通过链表的形式,遍历树形结构,优化了执行过程。
虚拟 DOM 和 diff算法
虚拟 DOM
React 渲染 dom 的一种优化手段,其原理是利用 createFragment ,即“创造碎片”。所有的 DOM 操作都将在“碎片”中操作,直到操作完成,再一起渲染。
diff 算法
是 React 更新 dom 元素的一套算法,其核心思想是,比较“节点树”的各个节点,根据比较的结果来决定是否更新该节点。通过其优化手段,将更新树的时间复杂度从O(n^3)变为O(n)。
diff 策略
- 忽略跨组件的移动操作
- 同类组件创建相同树,不同类则创建不同类的数(React Component 和 function Component)
- 兄弟,key
分为3个更新策略 tree diff, component diff, element diff:
tree diff: 当遇到树形结构更新时,仅比较同层节点,起子节点仅有创建和删除。这就意味这,如果存在移动某个树形结构的中间节点,那么原树将直接删除该节点及其子节点,在新树中创建起节点及其子节点。另外,官方不建议存在跨节点的移动操作。
component diff: 当节点更新为不同类型的节点时,成为 dirty component,react 认为更新成不同类型的节点,其结构一定是不一样的(树形结构),因此直接删除该节点后,直接创建。
element diff: 计算兄弟节点直接的移动操作,由原来的将目标位节点删除再创建的更新策略,更改为通过标识key,判断是为原来的节点,另外也获得了是否产生移动操作的判断依据。
由此也得出了3条优化建议:
- 设置 key
- 不要将组件更新为不同类型
- 减少将兄弟组件从末尾移至头部的操作
Redux
待补充
React 优化
常见的优化细节:
- function 组件代替 class 组件 -> 为什么?参考1 参考2
- HOC
- 使用 redux 这类状态管理工具时,部分不公用的state 不用挂载在 model 中 --> 为什么?
- 首屏优化方案
- lazy 懒加载组件
- react-router 的 loading 改善体验
- ssr (service side rendering)和 csr (client side rendering)
- pureComponent 和 shouldUpdate 优化更新频率
- componentDidCatch 探测错误边界
- 通过 Fragment 减少标签深度
- render 函数中的变量声明提升到外面,减少 GC
React-Hooks
useState 相当于 class component 中的 setState。
function render() {
ReactDom.render( , document.getElementById('root'))
}
function myState(initState) {
let state = initState
const update = (newState) => {
state = newState
render()
}
return [state, update]
}
function App () {}
useEffect 相当于 class component 中的若干声明周期函数
为什么16.8加入Hook?或者为什么增加 function component?
- class component 复用状态会很难。HOC 虽然解决了,但是有嵌套地狱的问题。
- class component 声明周期如果存在更改,其他生命周期可能均需要更改。
- es6 的 class 不如 function 对初学者更友好
- this 的指向
站在设计者的角度来思考。
SSR
next 主要是用在 React。
npx create-next-app
react-router
根据 history 开发的无刷新路由器。
主要有3个类型:hashHistory、borwerHistory、memoryHistory,常用的是 hashHistory 和 browerHistory。
新版本是 react-router-dom,
import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";
/**
* The public API for a that uses HTML5 history.
*/
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return ;
}
}
export default BrowserRouter;
区别
略
browerHistory 与 hashHistory 区别
hash 路由是是根据改变 # 后面的锚点来刷新,通过 window.onhashChange 监听变化,从而根据路由处找到相应的文件
history 是通过 html5 的 window.history 这个 web API 来实现
hashHistory的特点:
- 使用的是 history.location.hash 即 history.location.hash = "abc" (相当于是 localhost/#abc)
- 只能通过修改 # 后面的地址实现刷新
- 通过 window.onhashchange 监听 hash 变化,window.addEventListener('onhashchange', hashchangeHandler)
- 不能想 window.history.go(-1) 这种方式,只能通过字符串改变 url
- 对搜索引擎不友好,且不好追踪
browerHistory的特点:
- 使用的是 History 对象,提供 go, back, forward 的方法
- 相同的 url 会发生更新,并且压入历史记录中
- 通过 push 和 replace 方法,实际是通过 pushState,replaceState 实现无刷新跳转。其中 pushState 会压入浏览器历史栈,即 History.length + 1,replaceState 则不会。
问题
使用 history 更新路由且正常渲染后,再刷新,会引起404。(待验证)
是由于 history 更新了 url,也就是访问地址,此时向后端服务器请求时,如果没有匹配地址的资源则会 404。
例如 nginx 配置 /test 地址访问。通过 history router 从 /test 跳转至 /say ,刷新时,服务器将找不到资源,可以通过配置父级路径规避掉。
window.location 也会更改 url 但是会引起刷新