一文解决前端面试⑤----迷人的React👉

836 阅读31分钟

写在前面

React作为当前国内前端两大框架之一,是我们必须掌握的,更是进大厂必不可少的,本文详细介绍了react的基础和原理,让你对react有更深的认识和见解

食用对象:初级前端
美味指数:😋😋😋😋😋

1.react事件机制 ⭐⭐⭐⭐⭐

采取事件合成模式的原因:

  1. 如果DOM上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响。
  2. React为了避免这类DOM事件滥用,将事件绑定在root统一管理,相当于事件委托,防止很多事件直接绑定在原生的dom元素上,造成一些不可控的情况
  3. React 想实现一个全浏览器的框架,为了实现这种目标就需要提供全浏览器一致性的事件系统,以此抹平不同浏览器的差异。

步骤

  • 事件绑定:当用户在为onClick添加函数时,React并没有将Click时间绑定在DOM上面。而是在root处监听所有支持的事件(相同于使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数)
  • 事件触发:当事件发生并冒泡至root处时,使用统一的分发函数dispatchEvent将指定函数执行

特点

  • 大部分事件代理到root上,达到性能优化的目的
  • 对于每种类型的事件,拥有统一的分发函数dispatchEvent
  • 事件对象是合成对象,不是原生的, 模拟出来DOM事件所有能力
  • event.nativeEvent可以得到原生事件对象
  • 在传递参数时,最后直接一个参数,即可接收event

合成事件和原生事件的区别

  1. 写法不同,在原生事件中,事件名称使用小写,而 React 中使用驼峰命名
  2. 阻止默认行为不同,在 HTML 中,阻止事件的默认行为使用 return false,而 React 中必须调用 preventDefault。
  3. 使用 JSX 语法时需要传入一个函数作为事件处理函数,而不是一个字符串;
  4. 机制不同,原生是直接将事件绑定到当前元素,React 中的事件机制则分为两个阶段:事件注册、事件分发。所有的事件都会注册到 root 上,当触发时,会采用事件冒泡的形式冒泡到root上面,然后React将事件封装给正式的函数处理运行和处理。

2. 什么是JSX:⭐⭐⭐⭐⭐

React.createElement('tag', null, child1, child2, child3)
  • jsx是JavaScript的一种语法扩展,充分具备JavaScript的能力
  • JSX会被babel编译为:React.createElement(),第一个参数是标签名,第二个是属性,第三个是孩子
  • JSX语法糖允许前端开发者使用我们最熟悉的类HTML标签语法来创建虚拟DOM,这个函数返回的是vnode,然后通过patch去渲染

3. react组件通信 ⭐⭐⭐⭐⭐

1. 采用props传递需要的信息

2. context:
在React中,数据传递一般使用props传递数据,维持单向数据流,当你不想在组件树中通过逐层传递props的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递 原理
由于组件 的 Context 由其父节点链上所有组件通 过 getChildContext()返回的Context对象组合而成,所以,组件通过Context是可以访问到其父组件链上所有节点组件提供的Context的属性。
应用场景
语言,主题等逻辑不复杂但是要一层一层往下传递
过程

  • 首先用React.createContext(默认值)创建一个context:xxx
  • 然后用这个xxx.Provider包裹组件,上面有一个value={需要改变且接收的值}
  • (class组件消费的时候消费组件.contextType = xxx(刚定义的context),然后可以直接获取this.context里的值)
  • (函数组件就用xxx.Consumer包裹内容,里面用一个函数就能得到value值)

3. 通过redux等进行全局状态管理

4. 自定义发布订阅模式

import { EventEmitter } from 'events';
组件1this.eventEmitter = emitter.addListener('changeMessage', (message) => {
    this.setState({
       message,
    });
});
组件2:
    handleClick = (message) => {
        emitter.emit('changeMessage', message);
    };

5. 通过路由传参

params
  this.props.history.push({pathname:"/path/" + name});
  读取参数用:this.props.match.params.name
query
  this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
  读取参数用: this.props.location.query.name
state
  this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
  读取参数用: this.props.location.query.state
search
  this.props.history.push({pathname:`/web/search?id ${row.id}`});
  读取参数用: this.props.location.search

优缺点:

  • params在HashRouter和BrowserRouter路由中刷新页面参数都不会丢失
  • state在BrowserRouter中刷新页面参数不会丢失,在HashRouter路由中刷新页面会丢失
  • query:在HashRouter和BrowserRouter路由中刷新页面参数都会丢失
  • query和 state 可以传对象

6. ref

原理: 通过 React 的 ref 属性获取到整个子组件实例,再进行操作,注意如果是hooks就必须绑定到DOM上,或者用useImperativeHandle

4. 说一下高阶组件和render props ⭐⭐⭐⭐⭐

高阶组件:

HOC是一种组件的设计模式,HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。
1.透传所有props {...this.props}
2.增加xxx属性
优点:逻辑复用、不影响被包裹组件的内部逻辑。
缺点:会增加组件层级,比如说透传成本,透传覆盖等

render prop:

render prop是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,具有render prop 的组件接受一个返回React元素的函数,将render的渲染逻辑注入到组件内部
优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。
缺点:无法在 return 语句外访问数据、嵌套写法不够优雅

5. react生命周期 ⭐⭐⭐⭐⭐

组件挂载阶段:

1. constructor:

  • 若没有显式定义它,会有一个默认的构造函数
  • 若显式定义了构造函数,我们必须在构造函数中执行 super(props),为了继承父亲的this对象,否则无法在构造函数中拿到this。
  • 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor。 constructor中通常只做两件事:
  • 初始化组件的 state
  • 给事件处理方法绑定 this

2. getDerivedStateFromProps

  • 是静态方法,所以不能在这个函数里使用 this
  • 有两个参数 props 和 state,分别指接收到的新参数和当前组件的 state 对象
  • 这个函数会返回一个对象用来更新当前的 state 对象,如果不需要更新可以返回 null。
  • 该函数会在装载时,接收到新的 props 或者调用了 setState 和 forceUpdate 时被调用。如当接收到新的属性想修改 state ,就可以使用。

3. render

render是React 中最核心的方法,一个组件中必须要有这个方法,它会根据状态 state 和属性 props 渲染组件

4. componentDidMount

会在组件挂载后(插入 DOM 树中)立即调用。

该阶段通常进行以下操作:

  • 执行依赖于DOM的操作;
  • 发送网络请求;(官方建议)
  • 添加订阅消息(会在componentWillUnmount取消订阅);

组件更新阶段:

当组件的 props 改变了,或组件内部调用了 setState/forceUpdate,会触发更新重新渲染,这个过程可能会发生多次

1. getDerivedStateFromProps

2. shouldComponentUpdate

  shouldComponentUpdate(nextProps, nextState) {
    if (nextState,count !== this.state.count) {
      return true;  //可以渲染
    }
    return false //不重复渲染
  }
  • SCU默认返回true,即React默认重新渲染所有子组件
  • 必须配合“不可变值”一起使用
  • 可先不用SCU,有性能问题时再考虑使用 SCU总结:
      SCU是一个钩子函数,可以在里面自定义是否重新渲染的逻辑,它返回一个布尔值,如果没有自定义每次都会默认返回false,利用这个生命周期函数可以让不需要更新的子组件不更新来提升渲染性能,正是因为这个,react的不可变值原则非常重要,每次setState都应需要保证原来state不改变,不然使用SCU或者PureComponent时浅比较就可能对数组和对象的变化出问题

3. render

4. getSnapshotBeforeUpdate

  • 有两个参数 prevProps 和 prevState,表示更新之前的 props 和 state
  • 它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。
  • 此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。

5. componentDidUpdate

组件更新后的生命周期,可以对DOM进行操作

该方法有三个参数:

  • prevProps: 更新前的props
  • prevState: 更新前的state
  • snapshot: getSnapshotBeforeUpdate()生命周期的返回值

组件卸载阶段

1. componentWillUnmount()

会在组件卸载及销毁之前直接调用,不应该在这个方法中使用 setState,因为组件一旦被卸载,就不会再装载,也就不会重新渲染。

作用:

  • 清除 timer,取消网络请求或清除
  • 取消在 componentDidMount() 中创建的订阅等;

错误处理阶段
1. componentDidCatch(error, info)

此生命周期在后代组件抛出错误后被调用。

它接收两个参数∶

  • error:抛出的错误。
  • info:带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息

6. 什么是受控组件和非受控组件 ⭐⭐⭐⭐

受控组件:
在使用表单来收集用户输入时,例如<input><select><textearea>等元素都要绑定一个change事件,当表单的状态发生变化,就会触发onChange事件,更新组件的state。
这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与它的value或checked属性相对应
缺点
表单元素的值都是由React组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种情况,出现了非受控组件。

非受控组件:

input的值不受state控制,只是有初始值defaultValue, 一般通过ref拿dom节点的值去拿,现用现取

使用场景:

  • 必须手动操作DOM元素,setState实现不了,比如文件上传
  • 某些富文本编辑器,需要传入DOM元素

7. 介绍一下setState? ⭐⭐⭐⭐⭐

setState是同步还是异步的?

异步:在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。
同步:在 React 无法控制的地方,比如原生事件,具体就是在 addEventListener、setTimeout、setInterval 等事件中,就只能同步更新。

setState调用过程:

  1. 在调用setState后,setState里有一个enqueueSetState 方法将新的 state 放进组件的状态队列里,并调用 enqueueUpdate ,传入将要更新的实例对象;
  2. 在 enqueueUpdate 方法里,会判断batchingStrategy对象的 isBatchingUpdates 属性,isBatchingUpdates默认为false,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段如果轮到执行,就调用 batchedUpdates 方法来直接发起更新流程。(batchingStrategy 或许正是 React 内部专门用于管控批量更新的对象。)
  3. 每当 React 调用 batchedUpdate 去执行更新动作时,会先把这个锁给“锁上”(置为 true),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents 里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。

哪些能命中batchUpdate机制:

  • 生命周期(和它调用的函数)
  • React中注册的事件(和它调用的函数)
  • React可以“管理”的入口 哪些不能命中batchUpdate机制:
  • setTimeout setInterval等(和它调用的函数)
  • 自定义的DOM事件(和它调用的函数)
  • React“管不到的入口”

不可变值:
不要直接改变state,需要使用setState,同时修改时不能影响原来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  //其他操作
  })

也可以用slice()后得到一个副本,然后进行操作,注意不能直接对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进行属性设置,这样违反不可变值

7. React中的props为什么是只读的? ⭐⭐⭐⭐

props只能从父组件流向子组件,React具有浓重的函数式编程的思想。
提到函数式编程就要提一个概念:纯函数。

它有几个特点:

  • 给定相同的输入,总是返回相同的输出。
  • 过程没有副作用。
  • 不依赖外部状态。 this.props就是汲取了纯函数的思想。props的不可以变性就保证的相同的输入,页面显示的内容是一样的,并且不会产生副作用

8. react性能优化 ⭐⭐⭐⭐

shouldComponentUpdate

  shouldComponentUpdate(nextProps, nextState) {
    if (nextState,count !== this.state.count) {
      return true;  //可以渲染
    }
    return false //不重复渲染
  }
  1. SCU默认返回true,即React默认重新渲染所有子组件
  2. 必须配合“不可变值”一起使用
  3. 可先不用SCU,有性能问题时再考虑使用 SCU总结: SCU是一个钩子函数,可以在里面自定义是否重新渲染的逻辑,它返回一个布尔值,如果没有自定义每次都会默认返回false,利用这个生命周期函数可以让不需要更新的子组件不更新来提升渲染性能,正是因为这个,react的不可变值原则非常重要,每次setState都应需要保证原来state不改变,不然使用SCU 或者PureComponent时浅比较就可能对数组和对象的变化出问题

PureComponent
在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行shouldComponentUpdate。不过,pureComponent中的 shouldComponentUpdate()进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致,这样就省去虚拟DOM的生成和对比过程,达到提升性能的目的

9. 什么是 Fragments: ⭐⭐⭐

  • React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
  • 在React中,我们需要有一个父元素,同时从组件返回React元素。有时在DOM中添加额外的节点会很烦人。使用 Fragments,我们不需要在DOM中添加额外的节点。我们只需要用 React.Fragment 或才简写 <> 来包裹内容就行了

10. 介绍一下传送门Portal: ⭐⭐⭐

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案

ReactDOM.createPortal(child, container)

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。

使用场景:

  • 父组件有overflow:hidden,想逃离父组件
  • 父组件z-index值太小
  • fixed的元素要放到body第一层,有更好的浏览器兼容性
return ReactDOM.createPortal(
  this.props.children,
  domNode
);

11. 类组件与函数组件有什么异同? ⭐⭐⭐

相同点:

组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。 呈现效果一致,可相互改写

不同点:

  1. 类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念
    函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。
  2. 性能优化上,类组件主要依靠 shouldComponentUpdate阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。
  3. 类组件更容易上手,从未来趋势上看,由于React Hooks 的推出,函数组件成了社区未来主推的方案。
  4. 类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的未来发展。

12. React和Vue的区别 ⭐⭐⭐⭐

共同点:

  • 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库
  • 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板。
  • 都使用了Virtual DOM(虚拟DOM)提高重绘性能
  • 都有props的概念,允许组件间的数据传递
  • 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性 不同点:
  1. 数据流:Vue默认支持数据双向绑定,而React一直提倡单向数据流
  2. 虚拟DOM:
  • Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
  • 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。
  1. 模板不同:React使用JSX拥抱JS,Vue使用模板拥抱html
  2. 实现监听数据变化的原理不同
  • Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。
  • React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。
  1. 扩展方面:
  • react可以通过高阶组件来扩展
  • 而vue需要通过mixins来扩展

13. 介绍一下虚拟DOM和Diff算法 ⭐⭐⭐⭐⭐

使用虚拟dom的原因

  • 传统开发模式中,操作DOM时会从构建DOM树从头到尾走一遍流程,操作DOM时他不知道后面还会不会有DOM操作,操作十次就构建了十次,前面构建的就浪费了,这样白白浪费了性能。
  • 虚拟DOM是将真实的DOM节点用JavaScript模拟出来,js对象表示dom结构,对象记录了dom节点的标签、属性和子节点将DOM变化的对比,放到 Js 层来做。
  • 用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。

diff算法:

调和: 将Virtual DOM树转换成actual DOM树的最少操作的过程,diff算法是调和的具体实现。

diff策略

React用 三大策略 将O(n^3)复杂度 转化为 O(n)复杂度

  1. 策略一:忽略节点跨层级操作场景,两棵树只对同一层次节点,如果一个DOM节点在前后两次更新中跨越了层级,那么React不会尝试复用他。
  2. 策略二:默认拥有相同类的两个组件 生成相似的树形结构,拥有不同类的两个组件 生成不同的树形结构。比对时:两个不同类型的元素会产生出不同的树。如果元素由div变为p,React会销毁div及其子孙节点,并新建p及其子孙节点
  3. 策略三:开发者可以通过 key prop来暗示哪些子元素在不同的渲染下能保持稳定,对于同一层级的一组子节点,通过唯一id区分。

Diff流程

  1. 从Diff的入口函数reconcileChildFibers出发,该函数会根据newChild(即JSX对象)类型调用不同的处理函数
  2. 当newChild类型为object、number、string,代表同级只有一个节点,会进入单节点Diff处理,当newChild类型为Array,同级有多个节点,会进入多节点Diff处理
  3. 单节点Diff中首先判断是否存在对应的DOM节点,如果存在就判断能否复用,能否复用是通过key和type判断的,如果能复用就返回之前的副本,如果不能就删除节点并生成新的
  4. 多节点Diff中,有三种情况:节点更新,节点新增或减少,节点位置变化,会先判断情况,再决定走哪个逻辑,更新,新增还是删除,此时有两轮遍历,第一轮遍历:处理更新的节点。第二轮遍历:处理剩下的不属于更新的节点。

14. React怎么达到数据持久化 ⭐⭐⭐

  • 通过redux存储全局数据时,如果用户刷新了网页,就会被全部清空,比如登录信息等。这时就会有全局数据持久化存储的需求。
  • 首先想到的就是localStorage,localStorage是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。但是在已经使用redux来管理和存储全局数据的基础上,再去使用localStorage来读写数据,这样不仅是工作量巨大,还容易出错。
  • 所以用redux-persist。redux-persist会将redux的store中的数据缓存到浏览器的localStorage中

15. react SSR服务端渲染 ⭐⭐⭐

客户端渲染:

首先请求html,然后下载html里的js/css文件,等待js加载完后,再向服务器请求数据,数据返回后客户端从0到完整得渲染

缺点:

  • 由于页面显示过程要进行JS文件拉取和React代码执行,首屏加载时间会比较慢。
  • 对于SEO(搜索引擎优化),完全无能为力,因为搜索引擎爬虫只认识html结构的内容,而不能识别JS代码内容

服务端渲染:

首先请求html,然后服务端请求数据,在服务端初始渲染,返回具有正确内容的界面,再请求js/css,返回后把剩下一小部分渲染完

优点:

  • 客户端渲染下,除了加载html,还要等待js/css加载完成,之后执行js渲染出页面,这个期间用户一直在等待
  • 服务端只需要加载当前页面的内容,而不需要一次性加载全部的 js 文件。等待时间大大缩短,首屏加载变快。

缺点:

  • 不利于前后端分离,开发效率低。
  • 占用服务器资源。

16. React.createClass与React.Component区别: ⭐⭐⭐

  1. 函数this自绑定
  • React.createClass创建的组件,其每一个成员函数的this都有React自动绑定,函数中的this会被正确设置。
  • React.Component创建的组件,其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。
  1. 组件属性类型propTypes及其默认props属性defaultProps配置不同
  • React.createClass在创建组件时,有关组件props的属性类型及组件默认的属性会作为组件实例的属性来配置,其中defaultProps是使用getDefaultProps的方法来获取默认组件属性的
  • React.Component在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。
  1. 组件初始状态state的配置不同
  • React.createClass创建的组件,其状态state是通过getInitialState方法来配置组件相关的状态;
  • React.Component创建的组件,其状态state是在constructor中像初始化组件属性一样声明的。

17. react渲染问题 ⭐⭐⭐⭐

哪些方法会触发 react 重新渲染:

  1. setState()方法被调用
  • 当 setState 传入 null 时,并不会触发 render
  1. 父组件重新渲染
  • 只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render

重新渲染 render 会做些什么?

  1. 会对新旧 VNode 进行对比,也就是我们所说的Diff算法。
  2. 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面
  3. 遍历差异对象,根据差异的类型,根据对应对规则更新VNode
  4. React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用

React如何判断什么时候重新渲染组件?

  • 组件状态的改变可以因为props的改变,或者直接通过setState方法改变。
  • 组件获得新的状态,然后React决定是否应该重新渲染组件。只要组件的state发生变化,React就会对组件进行重新渲染。
  • 因为React中的shouldComponentUpdate方法默认返回true,这就是导致每次更新都重新渲染的原因
  • 需要重写shouldComponentUpdate方法让它根据情况返回true或者false来告诉React什么时候重新渲染什么时候跳过重新渲染

18. React Router有哪两种模式 ⭐⭐⭐⭐⭐

hash模式(默认)

abc.com/#/user/10

  1. hash 模式是一种把前端路由的路径用井号 # 拼接在真实 URL 后面的模式。当井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 hashchange 事件,不会被包括在 HTTP 请求中。
  2. 在hash模式下,所有的页面跳转都是客户端进行操作,不需要后端的支持,因此对于页面拦截更加灵活,但每次url的改变不属于一次http请求
  3. hash的改变不会导致页面的刷新,通过window.onhashchange监听hash的改变,借此实现无刷新跳转的功能 H5 history

abc.com/user/10

  1. 切换历史状态包括back、forward、go,修改历史状态包括了 pushState, replaceState两个方法,通过pushState把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原
  2. 前端的URL必须和向发送请求后端URL保持一致,否则会报404错误。不怕前进,不怕后退,就怕刷新,f5,(如果后端没有准备的话,会返回404),因为刷新是实实在在地去请求服务器的。
  3. 使用简单,比较美观

19. react-router 实现的思想: ⭐⭐⭐

基于 history 库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知,通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render

20. 如何配置 React-Router 实现路由切换: ⭐⭐⭐

  1. 使用<Route> 组件
  2. 结合使用 <Switch> 组件和 <Route> 组件,一个 <Switch> 会遍历其所有的子 <Route>元素,并仅渲染与当前地址匹配的第一个元素。
  3. 使用 <Link>、<NavLink>、<Redirect> 组件
  • Link:<Link> 组件来在你的应用程序中创建链接。无论你在何处渲染一个<Link> ,都会在应用程序的 HTML 中渲染锚(<a>
  • NavLink:<NavLink>会在匹配上当前的url的时候给已经渲染的元素添加参数(css样式等)
  • Redirect:<Redirect>组件实现路由的重定,属性一般使用from和to

21. react-router 里的 Link 标签和 a 标签的区别 ⭐⭐⭐

从最终渲染的 DOM 来看,这两者都是链接,都是标签 区别∶

  • <Link>一般配合<Route> 使用,react-router接管了其默认的链接跳转行为
  • <Link> 的“跳转”行为只会触发相匹配的<Route>对应的页面内容更新,而不会刷新整个页面。 <Link>做了3件事情:
  • 有onclick那就执行onclick
  • click的时候阻止a标签默认事件
  • 根据跳转href(即是to),用history (web前端路由两种方式之一,history 和 hash)跳转,此时只是链接变了,并没有刷新页面而<a>标签就是普通的超链接了,用于从当前页面跳转到href指向的另一 个页面(非锚点情况)。

22. React-Router如何获取URL的参数和历史对象(路由传值): ⭐⭐⭐

获取URL的参数:

  1. 路由配置还是普通的配置,如:'admin',传参方式如:'admin?id='1111''。通过this.props.location.search获取url获取到一个字符串'?id='1111',可以用url,qs,querystring,浏览器提供的api URLSearchParams对象或者自己封装的方法去解析出id的值。
  2. 动态路由传值:通过this.props.match.params.id 取得url中的动态路由id部分的值,除此之外还可以通过useParams(Hooks)来获取
  3. 通过query或state传值:在Link组件的to属性中可以传递对象{pathname:'/admin',query:'111',state:'111'},通过this.props.location.state或this.props.location.query来获取即可,缺点:刷新页面数据丢失

获取历史对象:

  1. useHistory();
  2. 使用this.props.history获取历史对象

23. 介绍一下Redux ⭐⭐⭐⭐⭐

Redux是React的一个状态管理库,它基于flux,简化了React中的单向数据流,状态管理完全从React中抽象出来。

工作流程:

  • 在React中,组件连接到redux ,如果要访问redux,需要派发一个action到 Reducer。
  • 当reducer收到action时,通过 switch...case 语法比较 action 中type。 匹配时,更新对应的内容返回新的 state。
  • 当Redux状态更改时,连接到Redux的组件将接收新的状态作为props。当组件接收到这些props时,它将进入更新阶段并重新渲染 UI。

Redux 的三大原则:

  1. 单一数据源(一个Redux应用只有一个store),也是单向的数据流;
  2. state只读(唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。);
  3. 使用纯函数(reducer)来修改state。

Redux中间件:

  • Redux 的中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,
  • 原本 view -→> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store
  • 在这一环节可以做一些"副作用"的操作,如异步请求、打印日志等
  • redux中间件接受一个对象作为参数,对象的参数上有两个字段 dispatch 和 getState,分别代表着 Redux Store 上的两个同名函数。
  • 柯里化函数两端一个是 middewares,一个是store.dispatch

24. Redux 和 Vuex 有什么区别,它们的共同思想 ⭐⭐⭐⭐

共同点:

  1. 都是通过store来作为全局状态存储对象。
  2. 不能直接修改Store中状态,(vuex中的mutation、redux中的reducer),只允许同步操作; 不同点:
  3. Vuex取消了Redux中Action的概念。不同于Redux认为状态变更必须是由一次"行为"触发,Vuex仅仅认为在任何时候触发状态变化只需要进行mutation即可。Redux的Action必须是一个对象,而 Vuex认为只要传递必要的参数即可,形式不做要求。
  4. Vuex也弱化了Redux中的reducer的概念。reducer在计算机领域语义应该是"规约",在这里意思应该是根据旧的state和Action的传入参数,“规约"出新的state。在 Vuex中,对应的是mutation,即"转变”,只是根据入参对旧state进行"转变"而已。

25. connect有什么作用: ⭐⭐⭐⭐

负责连接React和Redux

获取state

connect 通过 context获取 Provider 中的 store,通过 store.getState() 获取整个store tree 上所有state

使用

export default connect(mapStateToProps, mapDispatchToProps)(AppUI); //也可使用装饰器

mapStateToProps: 此函数将state映射到 props 上,因此只要state发生变化,新 state 会重新映射到 props。 这是订阅store的方式。

mapDispatchToProps: 用来建立组件的参数到store.dispatch方法的映射。触发action更新reducer,进而更新state,引起UI数据的变化

24. 介绍一下hooks特点,和类组件的区别 ⭐⭐⭐⭐

函数组件的特点:

  • 没有组件实例
  • 没有生命周期
  • 没有state和setState,只能接收props

class组件的问题:

  • 大型组件很难拆分和重构,很难测试(即class不易拆分)
  • 相同业务逻辑,分散到各个方法中,逻辑混乱
  • 复用逻辑变得复杂,如mixins,HOC,Render Props
  • 生命周期复杂

React Hooks 解决了哪些问题:

  • 在组件之间复用状态逻辑很难,Hook 使我们在无需修改组件结构的情况下复用状态逻辑
  • 复杂组件变得难以理解,比如componentDidMount里很多不同的逻辑,而hooks有助于关注分离,可以一个逻辑写在一堆
  • 难以理解的this指向,hooks里没有this
  • 复杂的生命周期

25. 介绍一下知道的Hooks ⭐⭐⭐⭐⭐

useState:

useState 是允许我们在 React 函数组件中添加 state 的一个 Hook

const [当前状态的值, 设置状态值的函数] = React.useState(初始值)

和类组件state区别: 类组件中的 state 只能有一个。一般是把一个对象作为一个 state,然后再通过对象不同的属性来表示不同的状态。 而函数组件中用 useState 则可以很容易地创建多个 state,更加语义化。

为什么 useState 要使用数组而不是对象: 为了降低使用的复杂度,返回数组的话可以直接根据顺序解构,而返回对象的话要想使用多次就需要定义别名了

Effect Hook:

让函数组件模拟生命周期,默认函数组件没有生命周期
函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期,使用Effect Hook把生命周期钩到纯函数中

useEffect

  • 没有依赖项,那它会在每次render之后执行
  • 模拟componentDidMount-useEffect依赖[]
  • 模拟componentDidUpdate-useEffect无依赖,或者依赖[a, b]
  • 模拟componentWillUnMount-useEffect中返回一个函数

ps:

  • 此时并不完全等同于WillUnMount,准确的说里面return的函数,会在下一次effect执行之前,被执行
  • 也就是说如果当前useEffect模拟的是Mount和Update,在props发生变化,Update前也会执行return里的

useEffect让纯函数有了副作用,副作用就是对函数之外造成影响,如设置全局定时任务,而组件需要副作用,所以需要useEffect钩到纯函数中

useEffect 与 useLayoutEffect 的区别:

  • 两者都是用于处理副作用,底层的函数签名是完全一致的
  • useEffect 是渲染完之后异步执行的,所以可能会导致闪烁
  • useLayoutEffect是同步执行的,执行时机是浏览器把内容真正渲染到界面之前,等它执行完再渲染上去,就避免了闪烁现象。
  • 也就是说我们最好把操作 dom 的相关操作放到 useLayoutEffect 中去,避免导致闪烁。

useCallback:

主要用于缓存函数,函数式组件每次任何一个 state 的变化都会导致整个组件刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能

useMemo:

主要用于缓存计算结果的值,一般是针对组件的, 减少组件的不必要更新.

useRef

  • 返回一个可变的 ref 对象,其.current 属性被初始化为传入的参数。
  • 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的 ref 对象都是同一个。 应用场景:
  1. 用于获取DOM节点
const btnRef = useRef(null), <button ref={btnRef}>
  1. 获取子组件的实例(只有类组件可用)
  2. 在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx

useContext:

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值

useReducer

useState 的替代方案,接收一个reducer,并返回当前的 state 以及与其配套的 dispatch 方法

适用场景:

  • state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state
  • 使用 useReducer 还能给那些会触发深更新的组件做性能优化

useReducer和redux的区别

  • useReducer是useState的代替方案,用于state复杂变化
  • useReducer是单个组件状态管理,组件通讯还需要props
  • redux是全局的状态管理,多组件共享数据

感谢阅读

非常感谢您到阅读到最后,如果有错误希望您能够指出,以免误导其他人,如果您觉得对您有帮助的话,希望能够点个赞,加个关注,有任何问题都可以联系我,希望能够一起进步。

最后祝您前程似锦,我们各自攀登,高处相见🌈!