# React关键
我们使用react16.8,也是从这个版本开始,有了hooks
# 基础提纲
React中函数组件和类组件的区别。
- 这里描述的是在 React 中 function 和 class 的差别,差异本身和React Hooks无关。
- 本质区别就在于:function components 所拥有的
捕获渲染值(Capture Value)
特性- Function Component 是更彻底的状态驱动抽象,甚至没有生命周期的概念,只有一个状态,而 React 负责同步到 DOM。
- 既然是状态同步,那么每次渲染的状态都会固化下来。
- 「Capture Value」具体是啥意思?
- 函数式组件,每次 Render 都有自己的 Props 与 State,可以认为每次 Render 的内容都会形成一个
快照
并保留下来,因此当状态变更而 Rerender 时,就形成了 N 个 Render 状态,而每个 Render 状态都拥有自己固定不变的 Props 与 State。这就是 Capture Value 特性。即函数式组件中的props是不可变的。 - 而类组件,因为使用this.props来访问状态,且this是可变的(每次渲染都不同),所以this.props总是访问最新的props。
- 函数式组件,每次 Render 都有自己的 Props 与 State,可以认为每次 Render 的内容都会形成一个
- 另外,总得来说函数式组件:
没有实例,没有生命周期,没有state
React事件机制
- 什么是合成事件
- event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
- event.nativeEvent 是原生事件对象
- 所有的事件,都被挂载到 document 上
- 和 DOM 事件不一样,和 Vue 事件也不一样
- event参数
- 默认传参
- 自定义参数:除额外参数外,最后追加一个event参数,即可接收
- 「与vue对比」
- 这里的
event是封装的合成事件SyntheticEvent
- event.target:返回当前触发元素
- event.currentTarget:返回当前触发元素
- event.nativeEvent.target:返回当前触发元素
- event.nativeEvent.currentTarget:返回document,说明
事件是被注册到document上的
- 这里的
- 为什么要合成事件
- 减少内存消耗,提升性能,不需要注册那么多的事件了,一种事件类型只在 document 上注册一次,避免频繁解绑
- 统一规范,解决 ie 事件兼容问题,简化事件逻辑
- 方便事件的统一管理(如事务机制)
- 合成事件与原生事件的执行顺序,能否混用
- 合成事件的注册和分发执行(
addEventListener
、dispatchEvent
、listenerBank
) - React中为什么需要绑定this?有几种绑定方法?
- 源码的invokeGuardedCallback函数中,事件处理函数作为回调函数是直接调用的,
并没有指定调用的组件
,所以不进行手动绑定的情况下this会回退到默认绑定,所以我们需要手动将当前组件绑定到 this上。
- 源码的invokeGuardedCallback函数中,事件处理函数作为回调函数是直接调用的,
- 什么是合成事件
React生命周期
15(16)
- 初始化阶段
- constructor
- 挂载
- componentWillMount(替换为static getDerivedStateFromProps)
- render
- componentDidMount
- 更新
- componentWillReceiveProps(替换为static getDerivedStateFromProps)
- shouldComponentUpdate
- componentWillUpdate(删掉)
- render
- (getSnapshotBeforeUpdate)
- componentDidUpdate
- 卸载
- componentWillUnmount
- 错误处理
- (componentDidCatch)
- 初始化阶段
废弃的 componentWillMount() 可用componentDidMount()来代替,或者constructor替代;
- 放在didMount里,是为了保证componentWillUnmount的在组件移除时执行,防止内存泄漏
废弃的 componentWillReceiveProps() 可用 getDerivedStateFromProps(nextProps, prevState) 代替
废弃的 componentWillUpdate() 可用 getSnapshotBeforeUpdate() 替代
由于Fiber在新版本中会支持
异步渲染的特性
,而16 版本 render 之前的生命周期可能会被多次执行(componentWillMount、componentWillReceiveProps、componentWillUpdate这3个生命周期钩子,在异步渲染模式下会有一些潜在的问题)因此被弃用。getDerivedStateFromProps
- getDerivedStateFromProps接收最新的Props值nextProps、上一个state值prevState两个参数
- 返回一个对象来更新state,或者返回null表示不需要更新state。
- 要注意的是,getDerivedStateFromProps 是一个静态方法,纯函数,不能访问this,所以如果要跟上一个props值作比较,只能是把上一个props值存到state里
React的请求应该放在哪个生命周期中?
- 官方推荐的异步请求是在componentDidmount中进行.
- 如果有特殊需求需要提前请求,也可以在特殊情况下在constructor中请求
- React的异步请求到底应该放在哪个生命周期里,有人认为在componentWillMount中可以提前进行异步请求,避免白屏,其实这个观点是有问题的.
- 由于JavaScript中异步事件的性质,当启动API调用时,浏览器会在此期间返回执行其他工作。当React渲染一个组件时,它不会等待componentWillMount它完成任何事情
- React继续前进并继续render,没有办法“暂停”渲染以等待数据到达。
- 而且在componentWillMount请求会有一系列潜在的问题
- 首先,在SSR端渲染时,如果在 componentWillMount 里获取数据,fetch data会执行两次,一次在SSR端,一次在CSR端,这造成了多余的请求;
- 其次,在React 16进行React Fiber重写后,componentWillMount可能在一次渲染中多次被调用
React 按需加载
- 使用 react-loadable库,项目中对齐进行封装
- 使用 React.lazy,返回一个 thenable 对象,拥有3个enum状态,分别对应 Promise 的3种状态。
Refs 3种使用方式(字符串、回调refs、createRef())
- 你不能在函数组件上使用 ref 属性,因为他们没有实例。
Virtual Dom 的「优势」在于
- 无需手动操作dom
- 保证性能下限
- 可跨平台
hooks的好处是啥?
- 从类组件的一些弊端说起:
- 1、**「难以复用的状态逻辑」**复用一个有状态的组件太麻烦,虽然有 渲染属性(Render Props) 和 高阶组件(Higher-Order Components),但这些方式会增加很多组件层级嵌套;而使用 hooks,没有多余的层级嵌套,把各种想要的功能写成一个一个可复用的自定义hook,直接在组件里调用这个hook即可。
- 2、**「混乱的生命周期、混乱的副作用」**生命周期钩子函数里的逻辑太乱。我们希望的是,一个函数只做一件事,但生命周期钩通常做了很多不同的事,甚至在不同的钩子中还会做相同的事。
- 3、**「this指向困扰」**class 的使用让人困惑,要繁琐的处理this绑定问题;另外,如果一个无状态function组件需要有自己的state时,又需要改成class组件。。而hooks没有这样的烦恼
- 从类组件的一些弊端说起:
hook的“形态”类似被否定掉的Mixins方案,二者有什么本质不同?
- 二者都是提供一种「插拔式的功能注入」
- 不同的是:
- mixins是让多个Mixins共享一个对象的数据空间,会导致不同的Mixins依赖的状态产生冲突
- 而hooks是用在function中的,每一个hook都是相互独立的。。。不同组件调用同一个hook也可以保证自己的状态独立性
useState
- 状态更新方式的不同
- this.setState()做的是合并状态后返回一个新状态,而useState是直接替换老状态后返回新状态。
- 如何记住之前的状态
- React是通过 类似单链表形式的memoizedStates变量,通过 next 按顺序串联所有的 hook的:
- 怎么保证多个useState的相互独立的?(同样适用于useEffect)
- 使用 memoizedStates数组,来解决 Hooks 的复用问题
- react根据useState出现的顺序,依次收集相互独立的state到memoizedStates数组中,从而保证相互间的独立性。
- 这也是为什么,hooks必须写在函数的最外层,而不能写在循环或条件语句中。
- 状态更新方式的不同
useEffect
- 是用来处理副作用函数的,它相当于类组件的3个生命周期钩子(componentDidMount、componentDidUpdate、componentWillUnmount),以一抵三。
- 使用中,应该给每一个副作用一个单独的useEffect钩子
- 每次渲染,都会执行useEffect中的副作用函数
- 且执行时机是在dom更新之后
- useEffect中的副作用函数是异步执行的(不会阻碍浏览器更新视图),而之前的componentDidMount或componentDidUpdate中的代码则是同步执行的。
- 副作用函数callback的执行次数(3种情况):
- 如果 deps 不存在,那么 callback 每次 render 都会执行;
- 如果 deps 存在,只有当它发生了变化,callback 才会执行;
- 如果 deps 为[],则相当于只有componentDidMount,只在 首次render 后执行;
- 传入的deps依赖,可以是变量也可以是函数。如果传入的是组件内的函数,每次渲染都会重新生成这个函数,如果订阅了它就会触发副作用函数的执行,这也是useCallback被使用的原因。
- 如果订阅的函数F包含了变量A,那么副作用函数只需要订阅F就可以了,不需要订阅A,因为A的变化肯定会触发F的变化
- useEffect解绑副作用
- 场景:避免内存泄漏
- 方法:使useEffect的副作用函数A返回一个清理函数B即可
- 返回的这个清理函数B,将会在组件下一次重新渲染之后,在副作用函数A之前执行;
- 与componentWillUnmount只会在组件销毁前执行一次不同的是,副作用函数A及其可选的清理函数B在每次组件渲染都会执行。
useContext(MyContext)
- 仍然需要配合
<MyContext.Provider>
来使用 - 相当于 class 组件中的
static contextType = MyContext
或者<MyContext.Consumer>
- 仍然需要配合
useReducer
- 相较于 useState,它更适合一些逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等等的特定场景
- const [state, dispatch] = useReducer(reducer, initialArg, init);
- 第一个参数是 一个 reducer,就是一个函数类似 (state, action) => newState 的函数,传入 上一个 state 和本次的 action;
useCallback(() => doSomething(a, b), [a, b]);
- 返回一个 memoized 回调函数。useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
- 用于对不同 useEffect 中存在的相同逻辑的封装
- 仅在某个依赖项改变时才会更新
useMemo
- useMemo 和 useCallback 几乎是99%相似的
- 仅有的不同点是:
- useCallback是根据依赖(deps)缓存第一个入参的(callback)。useMemo是根据依赖(deps)缓存第一个入参(callback)执行后的值。
- 即useCallback缓存的是cb,而useMemo缓存的是cb执行后的值的。
- 传入 useMemo 的函数会在渲染期间执行,这与useEffect在渲染之后执行不同。因此 useMemo 内不要有与渲染无关的操作
useRef
- useRef 返回一个 可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。
- 本质上,useRef 就像是可以在其 .current 属性中保存一个任意可变值的“盒子”。
- 返回的 ref 对象在组件的整个生命周期内保持不变
React 性能优化
- 主要两个方向:
- 减少重新render:
- 类组件中使用 SCU、PureComponent
- 函数式组件使用
- React.memo(包裹子组件,props不变就不重新渲染),对标PureComponent
- useCallback(解决props如果包含callback,函数式组件每次重渲都会重新生成callback,即callback的引用变化,即使用React.memo包裹还是会触发子组件重渲。。useCallback得到一个缓存的函数,同一个引用)
- 减少重复计算:
- 使用 useMemo包裹大计算量函数,缓存计算结果。类似于Vue的计算属性。
- 默认是浅对比,注意当传入第二个参数来自定义比较时,与SCU返回的bool值刚好相反。
- 使用 useMemo包裹大计算量函数,缓存计算结果。类似于Vue的计算属性。
- 减少重新render:
- 主要两个方向:
SCU 跟 immutable 强相关,如何理解 react 的 immutable?
- JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。因此,对象值改变时引用地址却不变,这时使用PureComponent进行浅比较,只比较一层,SCU无法正确做出判断。 当然,可以在SCU中通过 深拷贝deepCopy 和 深层比较deepCompare来正确判断,但这些操作非常耗性能。
- 综上,React社区推荐React和Immutable.js库配套使用。使用immutable可以生成不可变state。
- immutable的state,即在改变state的时候,需要重新生成一个对象去代替原来的state,而不是直接改原来的。
- 并且,目前的Immutable库,都实现了
结构共享
,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享,避免了deepCopy把所有节点都复制一遍带来的性能损耗。比较两个Immutable对象是否相同,只需要使用===
就可以轻松判别。
- 并且,目前的Immutable库,都实现了
- immutable的state同样也决定了不能直接修改state
- 直接修改state会导致state不可预期。要知道setState本质是通过
一个队列机制
实现state的批处理更新的
。执行setState时,会将需要更新的state合并后放入状态队列,而不会立刻更新state,队列机制可以批量更新state。 - 如果不通过setState而直接修改this.state,那么这个state不会放入状态队列中,下次调用setState对状态队列进行合并时,会忽略之前直接被修改的state,这样我们就无法合并了,而且实际也没有把你想要的state更新上去。
saveOrderList(state, {payload: items}) { return Object.assign({}, state, orderList: Immutable(items)); } saveOrderList(state, {payload: items}) { return {...state, orderList: Immutable(items)}; }
- 直接修改state会导致state不可预期。要知道setState本质是通过
- immutable的state,即在改变state的时候,需要重新生成一个对象去代替原来的state,而不是直接改原来的。
key
- 准确复用、快速
diff
- tree diff:两棵树 只对同层次节点 进行比较,不考虑跨层级
- component diff:拥有相同类的两个组件 生成相似的树形结构,拥有不同类的组件....
- element diff:对于同一层级的一组子节点,通过唯一key区分。
fiber
- fiber的出现,是React为了解决
「主线程阻塞」
的问题 「主线程阻塞」
是因为JS引擎与GUI引擎互斥执行,当react对较大的组件树进行协调算法时,会持续占用主线程,且协调过程一气呵成不能被打断。- Vue得益于其可以精确定位节点更新,所以采用的策略是,把更新做得足够快,理论上就不需要时间分片了。
- 而React采用的策略是,时间分片,通过某种调度,合理分配CPU资源,从而提高浏览器的用户响应速率,同时兼顾任务执行。
- fiber原理:
- React@15使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。即不能被打断。
- React@16 的 Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,让协调 过程变成可中断的,适时地让出CPU执行权。
- 综上:
- Fiber 也称「协程」,是一种控制流程的让出机制;
- Fiber 也称「纤维」,是一种数据结构或者说执行单元。
- Fiber结构
- 新增属性:
- 结构信息(上下文信息)
- 副作用(链表effectTag)
- 替身(WIP树)
- 新增属性:
- React@16 还是同步渲染的,因为没有开启并发模式 Concurrent。想要真正体会到 React Fiber 重构的效果(异步渲染),可能要等到 v17。v16 只是一个过渡版本。
- fiber的出现,是React为了解决
mobx
- 事件触发action,在action中修改state => 通过computed拿到更新的state的计算值 => 自动触发对应的reactions(包含autorun,渲染视图,when,observer等)
- 通过observable装饰器装饰一个属性,使用Object.defineProperty来拦截对数据的访问,一旦值发生变化,将会调用react的render方法来实现重新渲染视图的功能或者触发autorun等。
- redux和mobx的对比:
- mobx将数据保存在分散的多个store中;redux将数据保存在单一的store中
- mobx使用@observable(Object.defineProperty来拦截),数据变化后自动处理响应的操作;redux使用plain object保存数据,需要手动处理变化后的操作;
- mobx中的状态是可变的,可以直接对其进行修改;redux使用不可变状态,这意味着状态是只读的
React 和 Vue 的区别
- 相同点
- 都支持组件化
- 都是数据驱动视图
- 都是用 vdom 操作 DOM
- 不同点
- React 使用 JSX 拥抱JS,Vue使用模板拥抱 html
- React 函数式编程,Vue声明式编程,需要注册
- React 更多需要自力更生,Vue把想要的都给你
- 相同点
React
- 只是一个函数 UI = render(data),没有所谓的状态管理,而只是数据到视图的驱动
- react diffing算法思路
- 传统diff通过递归,算法复杂度达到O(n^3),而React diff算法是一种 调和 实现,通过 三大策略 进行优化,将O(n^3)复杂度 转化为 O(n)复杂度:
- 1、策略一(tree diff),只同层级比较
- 2、策略二(component diff),相同类的两个组件 生成相似的树形结构,不同类的两个组件 生成不同的树形结构
- 3、策略三(element diff):对于同一层级的一组子节点,通过唯一key区分。
- 传统diff通过递归,算法复杂度达到O(n^3),而React diff算法是一种 调和 实现,通过 三大策略 进行优化,将O(n^3)复杂度 转化为 O(n)复杂度:
- 虚拟Dom中的$$typeof属性的作用
- $$typeof属性,它被赋值为 REACT_ELEMENT_TYPE。
- ReactElement.isValidElement用来判断一个元素是否有效,其中会判断$$typeof。
- setState的执行机制
- 是异步的吗?(setState本身并不是异步的,而是 React的批处理机制给人一种异步的假象)
- 1、React的
生命周期
和合成事件
中,React仍然处于它的批处理更新机制中,这时无论调用多少次 setState,都不会立即执行更新 - 2、
异步代码
中调用 setState时,React的批处理机制已经走完,这时再调用完后就能立刻拿到更新后的结果。 - 3、在
原生事件
中调用 setState并不会触发 React的批处理机制,所以立即能拿到最新结果 - 最佳实践:
- 可通过第二个参数返回一个回调函数,此回调总是在批处理策略后执行,所以肯定能拿到更新后的值
- 1、React的
- 连续多次setState只有一次生效?(队列合并机制)
- 多次传入对象来更新时,React会将批处理机制中存储的多个setState进行合并
- 多次传入函数来更新时,每次都立即计算,后执行的函数会以前一次的函数更新结果为输入值
- 几个钩子中注意:
- componentDidMount直接调用 setState,会重复渲染过程
- 在componentWillUpdate、componentDidUpdate中setState,需要条件控制,否则会死循环
- 是异步的吗?(setState本身并不是异步的,而是 React的批处理机制给人一种异步的假象)
- react16新生命周期,有什么变化?
- 16 弃用了3个钩子函数: componentWillMount、componentWillUpdate、componentWillReceiveProps;
- 之所以移除,是因为Fiber在新版本中会支持异步渲染的特性,这几个钩子会有问题
- 新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate来代替;
- 新增了错误处理阶段:componentDidCatch
- 16 弃用了3个钩子函数: componentWillMount、componentWillUpdate、componentWillReceiveProps;
虚拟Dom比普通Dom 性能更好吗?
- VDOM也还是要操作 DOM,这是无法避免
- 如果是首次渲染的话,Vitrual Dom 甚至要进行更多的计算,消耗更多的内存。
- Vitrual Dom 的「优势」在于:
- 1、无需手动操作Dom,Diff算法 和 批处理策略Patch 会在一次更新中自动完成
- 2、保证性能下限
- 3、跨平台,本质上是JS对象,可以服务端开发,移动端开发等
- Vitrual Dom 的「缺点」只有一个:
- 无法进行极致优化
# JSX基本使用(略)
事件
- bind this
- event参数
- 传递自定义参数
表单
- 受控组件(因为react中没有vue的v-model,所以需要自己控制表单状态)
- 非受控组件
- 常见表单项
- 多行文本
- 注意不允许这样
<textarea>{{this.state.desc}}</textarea>
- 要这样
<textarea value={this.state.desc} onChange={this.onTextareaChange} />
- 多行文本
组件使用
- props传递数据
- props传递函数
- props类型检查
setState
- 不可变值(SCU对引用类型浅层比较无法得到正确判断,而深拷贝深比较耗性能,则state需要是不可变的,一般与immutable库配合使用)
- 可能是异步更新
- 合成事件和生命周期中,都是异步更新的
- 异步事件中是同步更新的,因为批处理已经执行了
- 原生事件中是同步的,因为没有批处理策略
- 可能会被合并
- 传对象是会被合并
- 传函数的时候不会合并
# React高级特性
函数组件
- 函数组件与类组件的区别
- 函数组件具有:
- 本质区别在于 capture values 捕获渲染值
- 纯函数,输入props,输出JSX
- 没有实例,没有生命周期,没有state
- 不能扩展其他方法
- 函数组件具有:
- 函数组件与类组件的区别
非受控组件
- 不使用value和onChange事件,而是用state作为 defaultValue defaultChecked
- 并且通过refs,手动操作dom,this.nameInputRef.current手动拿到值的变化
- 场景:必须操作DOM,setState实现不了时,只能使用非受控组件
- 文件上传《input type="file"》
- 富文本编辑器,需要传入DOM元素
Portals
- 组件默认会按照既定层次嵌套渲染,如何让组件渲染到父组件以外呢?
- ReactDOM.createPortal(组件, 渲染到的目标节点(比如document.body))
- 场景:
- 父组件设了BFC,比如overflow:hidden,会影响子组件展示,可以使用portal逃离父组件
- 父组件z-index太小,可以使用portal逃离父组件
- fixed的元素需要放在body第一层级时,可以使用portal逃离父组件
- 组件默认会按照既定层次嵌套渲染,如何让组件渲染到父组件以外呢?
props.children
context
- 公共信息传给每个子组件(用props太繁琐,用redux小题大做)
- React.createContext
异步组件(懒加载)
const Demo = React.lazy(() => import('./demo'))
<React.Suspense fallback={<div>loading....</div>}>
<Demo />
</React.Suspense>
性能优化
优化方向1:减少不必要的渲染(react默认父组件更新,子组件也无条件也更新)
- SCU(nextProps, nextState)
- 默认返回true,重渲。通过条件判断
nextProps.text !== this.props.text
,来跳过不必要的子组件更新
- 默认返回true,重渲。通过条件判断
- SCU与 “不可变值”强相关,必须配合 “不可变值” 使用!!!
- 即对于state的操作,不能违背不可变值的特性。。
- 怎样不违背不可变值?在setState前不能对state做修改,比如:先对state的list,push改变,之后再setState,此时在用SCU比较前后state时,state已经变了,则比较不出来就出现bug了
// 为了演示 SCU ,故意写的错误用法 this.state.list.push({ id: `id-${Date.now()}`, title }) this.setState({ list: this.state.list }) // 这也是为什么不能直接改变state,而需要再setState的时候再使用新值来赋值 // 不可变值(函数式编程,纯函数) - 数组 const list5Copy = this.state.list5.slice() list5Copy.splice(2, 0, 'a') // 拷贝了一个副本后,无论怎么修改都没有改变state this.setState({ list1: this.state.list1.concat(100), // 追加 list2: [...this.state.list2, 100], // 追加 list3: this.state.list3.slice(0, 3), // 截取 list4: this.state.list4.filter(item => item > 100), // 筛选 list5: list5Copy // 使用副本赋值,没有问题 }) // 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值 // 不可变值 - 对象 this.setState({ obj1: Object.assign({}, this.state.obj1, {a: 100}), obj2: {...this.state.obj2, a: 100} }) // 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值
- SCU对于对象或数组时,需要用_.isEqual等深度比较方法,这个是很耗费性能的,不建议这么用(如果建议的话React自己就帮我们做了)
- 有性能问题时再使用SCU,不是必须要用
- React实现了浅比较,大部分情况下够用了(尽量不要设计过深的数据结构)
- 类组件中使用
- PureComponent,会默认增加SCU,而如果用户手动定义SCU后,以自己定义的SCU为准
- 函数式组件使用
- React.memo(包裹子组件,props不变就不重新渲染),对标PureComponent
- useCallback(解决props如果包含callback,函数式组件每次重渲都会重新生成callback,即callback的引用变化,即使用React.memo包裹还是会触发子组件重渲。。useCallback得到一个缓存的函数,同一个引用)
- 类组件中使用
- immutable.js
- 如果要 彻底拥抱“不可变值”,则开发人员需要每次都对数据做 深拷贝和深比较,这样非常非常耗费性能
- 而immutable.js库就解决了这个问题,基于 共享数据(而不是深拷贝,只改变改变节点与其父节点),速度性能好
- 但是!!有学习成本和迁移成本,推荐按需使用
- 使用起来日常的js使用不是很一致,特别是和ES6的新的api是不一致的
- SCU(nextProps, nextState)
优化方向2:减少重复计算:
- 使用 useMemo包裹大计算量函数,缓存计算结果。类似于 Vue的computed。
- 默认是浅对比,注意当传入第二个参数来自定义比较时,与SCU返回的bool值刚好相反。
- 使用 useMemo包裹大计算量函数,缓存计算结果。类似于 Vue的computed。
公共逻辑抽离
- mixin已被react弃用
- 高阶组件HOC(类似于一个工厂模式,一个装饰器)
- 比如redux connect是高阶组件
import { connect } from 'react-redux'; const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList
- 比如redux connect是高阶组件
- Render Props:
使用一个值为函数的prop来传递需要动态渲染的nodes或组件
,属性名不一定是非叫render,其它命名依然有效jsx // 子组件依赖于父组件的某些数据时,需要将父组件的数据传到子组件,子组件拿到数据并渲染。 // render是一个函数组件 <DataProvider render={data => ( <Cat target={data.target} /> )}/> // Render Props,不是说非用一个叫render的props不可 // 习惯上可能常写成下面这种 <DataProvider> {data => ( <Cat target={data.target} /> )} </DataProvider>
- HOC vs Render Props
- HOC模式简单,会增加组件层级
- Render Props代码简洁,但学习理解成本稍高
Vue 如何实现高阶组件:https://www.jianshu.com/p/6b149189e035 - 为什么在 Vue 中实现高阶组件比较难? - 前面说过要分析一下为什么在 Vue 中实现高阶组件比较复杂而 React 比较简单。这主要是二者的设计思想和设计目标不同,在 React 中写组件就是在写函数,函数拥有的功能组件都有。而 Vue 更像是高度封装的函数,在更高的层面 Vue 能够让你轻松的完成一些事情,但与高度的封装相对的就是损失一定的灵活,你需要按照一定规则才能使系统更好的运行。
# Redux使用
创建action时,需要使用不可变值,纯函数
基本概念
- const store = createStore(reducer, applyMiddleware(thunk))
- reducer,纯函数,接收action返回新的state
- thunk中间件,可以逗号传入多个中间件
单向数据流
- 点击button(callback)=> dispatch(action)=> reducer(newState,返回全新state,不可变值) -> view改变
- subscribe 触发通知
react-Redux
- Provider
- connect
- 作为高阶组件,将 dispatch 作为props注入到 被包裹组件中
- AddTodo = connect()(AddTodo)
- mapStateToProps
- mapDispatchToProps
异步action
- 默认返回一个action对象
- 当有异步操作时,需要返回一个函数,其中有dispatch参数。此时就需要用到 中间件,比如redux-thunk
redux 中间件原理
- 中间件就是在单向数据流中,在dispatch的位置插入一些逻辑
- 点击button(callback)=> 【新的dispatch,增加mid1、mid2...】dispatch(action)=> reducer(newState,返回全新state,不可变值) -> view改变
mobx
- 事件触发action,在action中修改state => 通过computed拿到更新的state的计算值 => 自动触发对应的reactions(包含autorun,渲染视图,when,observer等)
- 通过observable装饰器装饰一个属性,使用Object.defineProperty来拦截对数据的访问,一旦值发生变化,将会调用react的render方法来实现重新渲染视图的功能或者触发autorun等。
- redux和mobx的对比:
- mobx中的状态是可变的,可以直接对其进行修改;redux使用不可变值,这意味着状态是只读的
- mobx将数据保存在分散的多个store中;redux将数据保存在单一的store中
- mobx使用@observable(Object.defineProperty来拦截),数据变化后自动处理响应的操作;redux使用plain object保存数据,需要手动处理变化后的操作;
# React-router
基本与vue-router差不多的
- hash模式
- HashRouter
- history模式
- BrowserRouter
- 路由懒加载
# 原理
vdom和diff,参考vue部分的要点,因为vue2、vue3、react的diff算法实现都不完全相同,所以只需要掌握基础原理
JSX本质(结合Vue模板编译来学习)
- JSX等同于Vue模板,Vue模板不是html,JSX也不是JS,JSX即createElement函数
- 类似于vue的 h函数,返回vnode:
- React.createElement(tag或组件, null, [child1, child2, child3, ...])
- React.createElement('div', {...}, child1, child2, child3, ...)
- React.createElement(List, 标签属性, child1, child2, '文本节点', ...)
- 注意:
- 为了区分tag和组件,需要把组件的首字母大写
- 使用babel转译JSX(注意对比vue是使用vue-template-compiler插件转译的),为render函数,返回vnode,最后执行patch
事件机制
setState 和 batchUpdate - 有时异步(react可以管理的“入口”,即合成事件和生命周期中),有时同步(react管不到的“入口”,即异步函数和原生自定义DOM事件中) - 有时合并(对象形式),有时不合并(函数形式)
- setState主流程
- 无所谓同步还是异步,关键是看能否命中 batchUpdate机制
- setState(newState存入pendding队列中)-> 判断是否处于 batch update(isBatchingUpdates) -> - Y(处于,即批处理执行完了) 则保存组件到dirtyComponents中 - N(不处于,即批处理未执行完)遍历所有dirtyComponents,调用updateComponent,更新pendding state or props
- batchUpdate机制
increase = () => { // 开始:处于batchUpdate // isBatchingUpdates = true // 任何其他操作 // 结束 // isBatchingUpdates = false }
- transaction(事务)机制
- 事务机制,即要完成某个操作,需要先定义一个开始逻辑,定义一个结束逻辑。
- 执行时先执行开始逻辑,再执行操作,最后要执行结束逻辑。
- 这个机制就服务于batchUpdate机制
- 事务机制,即要完成某个操作,需要先定义一个开始逻辑,定义一个结束逻辑。
- setState主流程
渲染/更新过程
- 初次渲染
- 有了props state
- 解析JSX为render函数
- 执行render函数,生成vnode,由patch(elem, vnode)渲染
- react中的
patch被拆分为两个阶段
- reconciliation阶段:执行diff算法,纯js计算
- fiber,会将reconciliation阶段进行任务拆分(commit阶段dom渲染,无法拆分)
- commit阶段:将diff结果渲染DOM
- reconciliation阶段:执行diff算法,纯js计算
- react中的
- 更新渲染过程
- setState(newState) --> 生成dirtyComponents(可能包含子组件)
- 重新执行render函数,生成newVnode,由patch(vnode, newVnode)渲染
- 为什么没有说vue中的patch分为两个阶段?
- 个人理解是vue中每个组件都是一个watcher,只针对组件内部进行diff比较,通过响应式来得知哪些组件需要diff
- 因此,在合理划分组件的情况下,diff比较量不会很大,所以
边比较边更新dom
;
- 初次渲染
# 真题
组件间如何通讯
- 父子组件props,传数据,传函数
- 自定义事件
- context广播
- Redux
JSX本质是什么
Context是什么,如何使用
SCU的用途
redux单向数据流(画图)
setState场景题
componentDidMount() {
// count 初始值为 0
this.setState({ count: this.state.count + 1 })
console.log('1', this.state.count) // 0
this.setState({ count: this.state.count + 1 })
console.log('2', this.state.count) // 0
// 注意,前两步异步,并且合并了,即count等于1
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log('3', this.state.count) // 2
})
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log('4', this.state.count) // 3
})
}
什么是纯函数
- 不可变值
- 返回一个新值,没有副作用
- 输入与输出类型一致
React组件生命周期
React发起ajax应该在哪个生命周期
- 同Vue,应该放在dom渲染完的生命周期上,componentDidMount
渲染列表,为什么要用key
- 同Vue,必须用key,且不能是index或random
- diff算法中通过tag和key来判断,是否是相同节点
- 通过key判断相同,移动,减少渲染次数,提升性能
函数组件与类组件的区别
- 函数组件具有:
- 纯函数,输入props,输出JSX
- 没有实例,没有生命周期,没有state
- 不能扩展其他方法
- !!!本质区别在于 capture values 捕获渲染值
- 函数组件具有:
受控组件与非受控组件
- 受控组件,表单的值受到state控制,因为没有v-model,需要自行监听onChange事件来更新state
- 非受控组件,不使用value和onChange事件,表单只接收state作为默认初值,表单的值需要通过ref获取dom来得到
何时使用异步组件,如何使用react-router配置异步路由
- 同Vue,加载大组件,路由懒加载
- React.lazy
- React.Suspense
redux 如何进行异步请求
- 使用异步action
- 同步action会直接返回一个action对象
- 异步action会在异步中dispatch一个action
- 使用redux-thunk
- 使用异步action
react事件和DOM事件的区别
- 所有事件都挂载到document上
- event不是原生的,是SyntheticEvent合成事件对象
- dispatchEvent统一分发事件对象
- listenerBank匹配
react性能优化
- 渲染列表时加key
- 自定义事件、DOM事件及时销毁
- 合理使用异步组件
- 减少函数bind this的次数
- 避免子组件重渲
- 合理使用SCU、PureComponent、React.memo、useCallback
- 避免重复计算
- 使用useMemo缓存计算结果
- 合理使用 Immutable.js
React 和 Vue 的区别
- 相同点:
- 都是组件化
- 都是数据驱动视图
- 都是用vdom操作DOM
- 区别:
- React使用JSX拥抱JS;Vue使用模板拥抱html
- React函数式编程;Vue声明式编程
- React需要自力更生;Vue把想要的都给你
- 相同点: