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只能向下传递。
2. React使用
2.1 虚拟DOM & diff & fiber
- 虚拟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调度。
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被覆盖。
const [state, setState] = useState(initialState)
- useEffect,集合componentDidMount,componentDidUpdate,componentWillUnMount生命周期。接收一个数组作为依赖项,在依赖项改变时调用fn,如果不传,则在每次componentDidUpdate会触发,如果传空数组,componentDidMount触发。
useEffect(fn, [])
- useLayoutEffect,类似于useEffect,但会阻塞页面渲染。
- useContext,它是以 Hook 的方式使用 React Context。
const context = useContext(Context)
- useReducer,语法糖跟reudx差不多
const [state, dispatch] = useReducer(reducer, initialArg, init)
- useRef
- useMemo, 常用的性能优化hook, 返回缓存变量,减少不必要渲染。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- useCallback,常用的性能优化hook, 返回缓存函数,减少不必要渲染。
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。
引用
Why You Should Use React.js For Web Development