React 理解

102 阅读13分钟

State

1.在函数/类组件中如何使用state

组件内部state适合只在本组件内部使用的state,优点就是灵活,随时定义即可用。缺点是难以实现在组件共享 函数组件内部state可以使用useState,useReducer定义 类组件组件内部可以使用this.state 定义,使用this.setState修改state。

组件外部state,也就是所谓的状态管理库。目前用的比较多的是Redux,MobX,DVA?Umi也算,但是DVA/Umi是在Redux的基础上封装的,AntD4 Form也是在外部定义的状态管理

8.比较函数组件与类组件的内部状态

不同点 API不同 类组件:this.$state ,this,setState 函数组件:useState,useReducer

存储方式不同 类组件的state存储在类组件实例与fiber上 函数组件的state存储在fiber的hook上

更新不同 setState时候,类组件的新的state与旧的state合并对象,而函数组件是新的state覆盖老的state。并且,在useState的setState中,新旧state相同,则函数组件拒绝更新。

组件中使用状态的时候,取值来源不同 类组件中使用状态,直接使用this.state,它的直接来源是类组件实例。 函数中组件使用状态,直接使用useState或则useReducer函数返回值数组的第0个元素,这个值来自fiber上的hook对象。 换句话说,如果想要获取类组件的新的状态值,可以直接访问this.state. 而如果想要获取函数组件中的一个新的状态值,必须重新执行useState或则useReducer函数,即必须执行函数组件。

16.useState与useReducer区别

这两个Hook都是用于函数组件内部定义状态,useReducer可以接收一个reducer函数,意味着可以把状态修改逻辑放在reducer函数中,还可以多次复用。 因此,对于简单的状态值定义,可以使用useState,如果状态值修改逻辑复杂,想要抽离出来或者复用修改逻辑,可以选择useReducer 使用useState的时候,如果state不发生变化,组件不会更新。但是useReducer反之

16.useState与useReducer返回一个数组

因为函数组件中可以多次使用useState/useReducer,可以定义多个不同state,数组可以一次返回state、setState,命名交给用户自定义。如果是对象再解构,那么state的命名就要写死了,用户想定义多个state的时候,还得自己改写名字,太麻烦了

4.Redux工作原理
5.redux中间件机制,如果不用中间件不能能实现异步

middleware只是包装了store的dispatch方法,技术上讲,任何middleware能做的事情,都可能通过手动包装dispatch调用来实现,但是放在同一个地方统一管理会使整个项目的扩展变得容易的多。

组件通信

9.组件如何通信以及不同通信方式的特点
  • 父子组件:props。缺点是不适合多层级的祖先与后代。
  • 兄弟组件:交给共同的父组件管理,缺点是一个子组件更新,导致整个父组件更新
  • 祖先/后代组件:Context。不适合大量使用,因为一旦Context value 发生改变,涉及所有组件都要更新,影响可能会比较广,因此项目谨慎使用,
  • 远亲组件:比较适合使用第三方状态管理库,Redux
32.Context使用方法

Context使用场景:当祖孙组件想要通信的时候,

  1. 创建Context对象,可以设置默认值。如果缺少匹配的Provider,那么后代组件将会读取这里的默认值。
  2. Provider传递value给后代组件
  3. 后代组件消费value:
  • contextType:只能用在类组件且只能订阅单一的Context来源
  • useConntext:只能用在函数组件或者定义Hook中
  • Consumer组件,无限制
2.什么是HOC/如何“修改”组件属性props

props是属性,用于父子通信,且props不可修改 如果对于组件的props不满意,可以使用HOC返回一个新的组件 HOC:高阶组件,它是个函数,接收组件作为参数,返回一个新的组件,是Hooks出现之前,常见的复用逻辑的手段,如react-redux的connect、router5的 withRouter,AntD3的createForm等

22.解释下useImperativeHandle场景

让用户可以把一个变量当做ref暴露出来,经常和forwardRef一起使用。

  1. 把ref暴露给父组件。
  2. 暴露一个重要的方法给父组件
11.组件的常见性能优化手段

复用组件 在协调阶段,组件复用的前提是必须同时满足三个条件:同一层级下,同一类型,同一个key值。所以我们要尽量保证这三者的稳定性

减少组件重新render 组件重新render会导致组件进入协调,协调的核心就是我们常说的vdom diff,协调本身就是比较耗时的算法,减少协调,复用旧的fiber节点,那么加快渲染完成的速度。

有以下方法:

  1. shouldComponentUpdate :component类组件的一个生命周期,当用户定义的这个函数并且返回false,则进入退出更新阶段
  2. PurComponent 更新前会自行浅比较新旧props与state是否改变,如果两者都没变,则进入退出更新,
  3. memo:这里的areaPropsEqual是个函数,用户可以自定义,如果没有自定义,默认使用浅比较,比较组件更新前后的props是否相同,如果相同,则进入退出更新阶段。
  4. useMemo,可以缓存参数,useCallback 使用
  5. 再说Context,Context本身就是一旦Provider传递的value变化,所有消费这个value的后代组件都要更新,因此应该尽量精简使用Context,不同的组件使用不同的Context 传递数据。
17.useRef与useState区别

useRef用于记录一个值,这个值在函数组件卸载前,值得都是同一个引用。比如上面图中的例子

useState用于定义一个state,当state改变,组件更新,state也是记录在hook.memoizedState

10.常用组件有哪些以及各自特点

函数组件,类组件,原生节点,Provider,Consumer,Fragment,Suspense,Portal等

声明周期

38.类组件生命周期,以及废除三个老生命周期
23.类组件的compoentDidMount与useEffect/useEffectLayoutEffect对比

赋值给useEffect 的函数会在组件渲染到屏幕之后延迟执行。

useEffectLayoutEffect函数签名与useEffect相同,但是会在所有的DOM变更之后同步调用effect。

compoentDidMount 执行时机同useEffectLayoutEffect

18.useEffect与useLayoutEffect用法和区别

useEffect(setup,dependencies)该Hook接收一个包含命令式,且可能有副作用代码的函数。

在函数组件主题内(这里指在React渲染阶段)改变DOM,添加订阅,设置定时器,记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的bug并破坏UI的一致性。

使用useEffect完成副作用操作。赋值给useEffect的函数会在组件渲染到屏幕之后延迟执行。默认情况下,effect将在每轮渲染结束后执行,但你可以选择让它在只有某些值改变的时候才执行

useLayoutEffect函数签名与useEffect相同,但他会在所有的DOM变更之后同步调用effect。可以使用它来读取DOM布局并同步触发重渲染。在浏览器执行重绘制之前,useLayoutEffect内部的更新计划将同步刷新。

setup函数返回值在源码中称为destroy,如果destroy使函数,则会在组件更新或者卸载前执行。经常用于清除订阅,清除定时器等操作。

19.useEffect与useLayoutEffect延迟、同步是什么意思

路由

27.react-router6 原理

router6 相比router5发生了很大变化,大部分API都变了。

如,相比router5,router6实现了配置式路由,渲染子组件需要手动渲染Outlet组件,404路由需要配置path=*,且放在任意位置都可以。

使用Context机制,从Router层传递传递navigator、location、match等参数给后代组件,同时BrowserRouter、HashRouter组件会监听location,一旦location变化,即路由变化,那么就会执行setState,导致组件更新,后代消费navigator、location、match等参数的组件也更新。

28.懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始化加载速度,减轻了它的总体体积,因为某些代码块可能永远不会加载

26.React Router中history,hash路由差别

   HashRouter最简单,因为服务端并不解析#之后的字符,但是前端可以监听#之后的字符的变化,因此前端可以根据这个变化决定渲染哪个组件。因此,使用HashRouter不需要服务器端的特殊支持。

   但是BrowserRouter就不同了,BrowserRouter使用HTML history API(pushState,replaceState 和 popState 事件),让页面的UI同步与URL。服务器端对不同的URL返回不同的HTML

React Hooks

20.为什么不能在循环,条件或嵌套函数中调用Hook

 React中每个组件都有一个对应的FiberNode,其实就是一个对象,这个对象是有个属性叫memoizedState。当组件是函数组件的时候,fiber.memoizedState上存储的就是Hooks单链表

单链表的每个hook节点没有名字或者key,因为除了它们的顺序,我们无法记录它们的唯一性。因此为了确保某个Hook是它本身,我们不能破坏这个链表的稳定性

13.为什么要引入Hooks,Hooks解决了什么样的问题

React出现最初,组件都是类组件,我们可以在类组件内部使用状态,使用父作用,而这些函数组件内部都做不到,因此以前的函数组件基本上只能作为静态组件展示,因此,哪怕刚开始你使用了函数组件,可能也要再修改为类组件。

类组件有以下缺点:

1.组件之间复用状态逻辑很难:React没有提供将可复用性行为“附加”到组件的途径(例如:把组件连接到store),因此我们只能使用render props 或者使用高阶组件(HOC),而这很容易形成嵌套地狱

2.复杂组件变得难以理解:类组件每个生命周期函数只能写一次,那么复杂组件的某个声明周期函数,如:componentDidMount里,可能会存在很多不相关但是不得不组合在一起的代码,这个时候很容易产生bug,并且导致逻辑不一致,主要看着也臃肿。

3.难以理解,初学经常找不到this。类组件也会给工具带来一些问题,比如:class 不能很好的压缩,并且使热重载出现不稳定的情况

Hooks的引入,使得在函数内部定义状态,使用副作用等成为可能,并且Hooks还带来了其他好处,比如使得可以使用自定义Hook复用状态逻辑,也可以定义多个副作用处理函数,完美解决了类组件臃肿的问题

14.什么是自定义Hook

以useXyz命名方式,用于React中复用状态逻辑,自定义Hook中可以像函数一样,使用所有的Hooks API

15.为什么Hook出现之后,函数组件可以定义state

hooks出现之前,函数组件内部无法定义state,主要是因为函数组件每次更新,定义在函数体里的值都要重新初始化,没法保存。

而hooks提供的useState或者useReducer可以让函数在组件内定义state,每一个hook都有个对应的hook对象,这个对象傻姑娘会储存状态值,reducer等值,这个hook对象又以单链表的数据结构存在fiber上,而fiber是React的虚拟Dom,存在内存中

React基础

34.Fiber是什么

Fiber是VDOM的一种表现形式。 传统的VDOM中,如React15 VDOM 和Vue3 VDOM,当父节点有多个字节点的时候,父节点标记子节点的属性children是个数组,在更新VDOM的过程中,我们会按照深度优先遍历的方式,自上而下,自左而右,遍历字节点。

但是随着React的演进,传统VDOM被淘汰,Fiber取而代之,Fiber与传统VDOM的不同之处主要体现在它的结构上,如child,return,sibling属性的添加

这种传统VDOM到Fiber的演进,被称之为stack reconciler 到 fiber reconciler的演进。

为什么会发生这种改变呢?

在传统的stack reconciler中,一旦任务开始,就无法停下,不管这个任务有多庞大,而这个时候如果来了更高优先级的任务,那么高优先级的任务无法得到立即处理,从而会出现卡顿现象。

如何解决这种问题呢?做任务分解、给任务添加优先级,即实现增量渲染,把渲染任务拆分成块,匀到多帧。这也就意味着一个任务执行完毕后,下个任务可能是它的下一个兄弟节点或叔叔节点,这个时候Fiber的链表结构就派上用场了。

37.JSX是什么

React中JSX语法描述视图(View),JSX是一个看起来很像XML的JavaScript语法扩展。当然你也可以选择直接使用React.createElement,但是写起页面手速会很慢。

React17以前,React中如果使用JSX,则必须倒入React,否则会报错,这是因为旧的JSX转换会把JSX转换为React.createElement.

当然,这并不完美,除了增加学习成本,还有无法做到的性能优化和简化,如createElement里还要动态做children的拼接,依赖于React的导入等等。

而React17带来了改变,可以让我们单独使用JSX而无需引入React。这是因为新的JSX转换不会将JSX转换为React.createElement,而是自动从React的package中引入新的入口函数并调用。另外此次升级不会改变JSX语法,旧的JSX转换也将继续工作。

39.React事件机制

我们在React中平常使用的onClick,onChange等写法,其实用的就是React中事件,我们通常称之为合成事件。

React自定义的合成事件机制,帮助用户解决了平台兼容性、事件委托等优化机制,用户只需关注些React本身就行了。

关于合成事件,React17 曾发生过一次变化,在17以前,事件是委托在document上的,但是实际上,React项目是可以作为其他子项目的。那么这个时候就会出现这样的问题,比如父项目在document层定义了事件,而React的事件也委托在document层,那么这两个事件就会发生交叉,出现bug。

React17解决了这个问题,把事件委托在了自己的container层。

关于事件委托

又称事件代理机制,这种机制是指把所有的子节点的事件绑定在父级,使用一个统一事件监听和处理函数。这样可以简化事件处理和回收机制,从而提升效率。