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值
const [state, setState] = useState(initialState)
- useEffect,集合componentDidMount,componentDidUpdate,componentWillUnMount生命周期。接收一个数组作为依赖项,在依赖项改变时调用fn,如果不传,则在每次componentDidUpdate会触发,如果传空数组,componentDidMount触发。DOM更新后触发
useEffect(fn, [])
- useLayoutEffect,类似于useEffect,同步执行,会阻塞页面渲染。
- useContext,它是以 Hook 的方式使用 React Context。
const context = useContext(Context)
- useReducer,语法糖跟reudx差不多
const [state, dispatch] = useReducer(reducer, initialArg, init)
- useRef
- useMemo, 常用的性能优化hook, 返回缓存变量,减少不必要渲染,类似于shouldComponentUpdate,DOM更新前触发。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- useCallback,常用的性能优化hook, 返回缓存函数,减少不必要渲染,类似于shouldComponentUpdate
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