Skip to content

Latest commit

 

History

History
614 lines (495 loc) · 36.7 KB

各种问题.md

File metadata and controls

614 lines (495 loc) · 36.7 KB

各种问题

React中的合成事件是什么?

what

合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。

JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。

事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。

why

这样做有两个好处:

  • 抹平了浏览器之间的兼容问题,更好的跨平台
  • 增加性能和可维护性:对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

原理

React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。

在React底层,主要对合成事件做了两件事:

  • 事件委派: React会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。
  • 自动绑定: React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。(React.createClass里面才会,es6的class里面不会)

React代码复用的三种方式

高阶组件(HOC)

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。

  • 优点∶ 逻辑复用、不影响被包裹组件的内部逻辑。
  • 缺点∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖。嵌套地狱。

render props

render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。

具有render prop 的组件接受一个返回React元素的函数,将render的渲染逻辑注入到组件内部。在这里,"render"的命名可以是任何其他有效的标识符。

class DataProvider extends React.Components {
  state = {
    name: 'Tom'
  }

  render() {
    return (
      <div>
          <p>共享数据组件自己内部的渲染逻辑</p>
          { this.props.render(this.state) }
      </div>
    );
  }
}

// 其实思路有点类似于把child组件当成render函数传给父组件
const renderChild = data => (<h1>Hello { data.name }</h1>);

<DataProvider render={ renderChild }/>
  • 优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。
  • 缺点:无法在 return 语句外访问数据、嵌套写法不够优雅。嵌套地狱。

Hook

它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义hook,可以复用代码逻辑。

  • 使用直观
  • 解决hoc的prop 重名问题
  • 解决render props 因共享数据 而出现嵌套地狱的问题
  • 能在return之外使用数据的问题

HOC

HOC的适用场景

  • 代码复用,逻辑抽象
  • 渲染劫持
    • 权限控制:利用高阶组件的条件渲染特性可以对页面进行权限控制。private route也是类似这样的实现方式。
    • 组件渲染性能追踪:借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录。
    • React.memo就是一个渲染劫持的HOC
  • State,prop的抽象和更改

HOC相比 mixins 有什么优点

HOC 和 Vue 中的 mixins 作用是一致的,并且在早期 React 也是使用 mixins 的方式。但是在使用 class 的方式创建组件以后,mixins 的方式就不能使用了,并且其实 mixins 也是存在一些问题的,比如:

  • 隐含了一些依赖,比如我在组件中写了某个 state 并且在 mixin 中使用了,就这存在了一个依赖关系。万一下次别人要移除它,就得去 mixin 中查找依赖。
  • 多个 mixin 中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就会覆盖掉,导致问题。
  • 雪球效应,虽然我一个组件还是使用着同一个 mixin,但是一个 mixin 会被多个组件使用,可能会存在需求使得 mixin 修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本

HOC 解决了这些问题,并且它们达成的效果也是一致的,同时也更加的政治正确(毕竟更加函数式了)。

HOC运用了什么设计模式

使用了装饰模式。特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。

Fiber

why

React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间不可以打断,就会一直占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。

what

fiber翻译过来叫做纤维,顾名思义,就是把整个render的过程(主要是dom diff过程)分成像纤维一样的,一个一个的小任务。这些小任务是一个一个进行,并且随时可以打断的,不像一个大的递归任务,是不可以打断的。具体来讲,就是把递归式dom diff,改成迭代式的,通过requestIdleCallback这个api,在浏览器空闲的时间来进行dom diff,从而大大优化了用户体验。

Setstate

setState的时候发生了什么

// 首先调用了setState 入口函数,入口函数在这里就是充当一个分发器的角色,根据入参的不同,将其分发到不同的功能函数中去;
ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

//enqueueSetState 方法将新的 state 放进组件的状态队列里,并调用 enqueueUpdate 来处理将要更新的实例对象;
enqueueSetState: function (publicInstance, partialState) {
  // 根据 this 拿到对应的组件实例
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  // 这个 queue 对应的就是一个组件实例的 state 数组
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  queue.push(partialState);
  //  enqueueUpdate 用来处理当前的组件实例
  enqueueUpdate(internalInstance);
}

// batchingStrategy 对象可以理解为“锁管理器”。这里的“锁”,是指 React 全局唯一的 isBatchingUpdates 变量,isBatchingUpdates 的初始值是 false,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate 去执行更新动作时,会先把这个锁给“锁上”(置为 true),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents 里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。
function enqueueUpdate(component) {
  ensureInjected();

  // 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段
  if (!batchingStrategy.isBatchingUpdates) {
    // 若当前没有处于批量创建/更新组件的阶段,则立即更新组件
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  // 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”
  dirtyComponents.push(component);
  ...
}

setState后发生了什么

在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。

在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

如果在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果

setState 是同步还是异步的

假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新dom diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。所以默认是异步的,但是在一些情况下是同步的。

setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同。在源码中,通过 isBatchingUpdates 来判断setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。

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

React中setState的第二个参数作用是什么

setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常建议使用 componentDidUpdate 来代替此方式。在这个回调函数中你可以拿到更新后 state 的值。

Component, Element, Instance 之间有什么区别和联系?

  • 组件Component: 可以通过多种方式声明。可以是带有一个render()方法的类,简单点也可以定义为一个函数。这两种情况下,它都把属性props作为输入,把返回的一棵元素树作为输出。
  • 元素Element: 在React里面可以理解为就是vdom的node。它是一个普通对象(plain object),描述了对于一个DOM节点或者其他组件component,你想让它在屏幕上呈现成什么样子。元素element可以在它的属性props中包含其他元素(译注:用于形成元素树)。创建一个React元素element成本很低。元素element创建之后是不可变的。
  • 实例: 一个实例instance是你在所写的组件类component class中使用关键字this所指向的东西(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。

函数式组件(Functional component)根本没有实例instance。类组件(Class component)有实例instance,但是永远也不需要直接创建一个组件的实例,因为React帮我们做了这些。

componentDidUpdateuseEffect(fn, [])的区别

componenDiMount在DOM渲染之前,useEffect在之后。

产生的问题
如果在useEffect里面改变了state导致重新渲染,那之前的渲染也会有跳一下,是不好的用户体验。

解决方案
可以用useLayoutEffect。useLayoutEffect 与 componentDidMount、componentDidUpdate 的调用阶段是一样的。但是,一般推荐一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect。

还有一点区别,就是useEffect可能拿不到更新后的数据。

componentDidMount() {
  someReoslve().then(() => console.log(this.state.value));      // 会拿到resolve以后最新的数据
}

// useEffect 会捕获 props 和state。所以即便在回调函数里,你拿到的还是初始的 props 和 state。如果你想得到“最新”的值,你可以使用 ref
const [value, setValue] = useState(...);
useEffect(() => someReoslve().then(() => console.log(value), []);     // 会拿到最开始的默认的value

哪些方法会触发 React 重新渲染

  • setState:注意,当 setState 传入 null 时,并不会触发 render。
  • 父组件重新渲染:只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render。

重新渲染 render 会做些什么? 会对新旧 VNode 进行对比,也就是我们所说的Diff算法。 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面 遍历差异对象,根据差异的类型,根据对应对规则更新VNode

React声明组件有哪几种方法

  • 函数式定义的无状态组件
  • ES5原生方式React.createClass定义的组件
  • ES6形式的extends React.Component定义的组件

一些区别: 与无状态组件相比,React.createClass和React.Component都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。

  • React.createClass创建的组件,其每一个成员函数的this都有React自动绑定,函数中的this会被正确设置。
  • React.Component创建的组件,其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。

对有状态组件和无状态组件的理解及使用场景

  • 当一个组件不需要管理自身状态时,也就是无状态组件(纯展示组件),应该优先设计为函数组件,因为不需要生命周期函数来浪费性能。
  • 其它情况的话,有了hook以后,还是用函数组件把。

React中可以在render访问refs吗

不可以,render 阶段 DOM 还没有生成,无法获取 DOM。DOM 的获取需要在 pre-commit 阶段和 commit 阶段。 react生命周期

对React的插槽(Portals)的理解

React 官方对 Portals 的定义:Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案

Portals 是React 16提供的官方解决方案,使得组件可以脱离父组件层级挂载在DOM树的任何位置。通俗来讲,就是我们 render 一个组件,但这个组件的 DOM 结构并不在本组件内。

使用场景
对话框,modal窗口,这样可以避免被遮挡。

语法

ReactDOM.createPortal(child, container);

在React中如何避免不必要的render

  • shouldComponentUpdate
  • pure component
  • React.memo。 在16以前没有memo的时候,可以用HOC模拟一个,其实memo也是创造一个hoc。

对 React context 的理解

在React中,数据传递一般使用props传递数据,维持单向数据流,这样可以让组件之间的关系变得简单且可预测。但是单项数据流在某些场景中并不适用。比如组件组件有很多层嵌套。比如redux,dispatch想要传给底层的node,不需要一层一层传下去。

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。 可以把context当做是特定一个组件树内共享的store,用来做数据传递。简单说就是,当你不想在组件树中通过逐层传递props或者state的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递

受控组件和非控组件

受控组件就是状态被外面组件的state同步。

  • 优点:react通过这种方式消除了组件的局部状态,使整个状态可控。(推介)
  • 缺点:比如有多个输入框,如果想同时获取到全部的值就必须每个都要编写事件处理函数,代码量变多

非受控组件就是自己的状态没有同步到外面的state,可以使用一个ref来从DOM获得表单值。

  • 优点:简单
  • 缺点:直接从原生的DOM获取值,那这个原生DOM就有了一个小宇宙,有了无法管理的局部状态。

例子 自己写过的组件库,当时先是用非受控组件写的,然后PR的时候要改成受控组件 react folder tree中的checkbox

refs的作用是什么?有哪些应用场景?

作用:用来访问React 元素或 DOM 节点。

  • 当 ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为他的 current 属性以创建 ref。
  • 当 ref 属性被用于一个自定义的类组件时,ref 对象将接收该组件已挂载的实例作为他的 current。
  • ref不能在函数组件上直接使用,因为函数组件没有实例

使用场景:

  • 处理焦点、文本选择或者媒体的控制
  • 触发必要的动画
  • 集成第三方 DOM 库

React组件的构造函数有什么作用?它是必须的吗?

构造函数主要用于两个目的:

  • 初始化本地状态
  • 将事件处理程序方法绑定到实例this上
constructor() {
  super();
  this.state = {    // 初始化本地状态
    liked: false
  };
  this.handleClick = this.handleClick.bind(this); // 将事件处理程序方法绑定到实例this上
}

super是必须的嘛?是。

super关键字表示父类的构造函数,用来新建父类的this对象。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

React.Component代码:

constructor(props) {
  // 所以子类必须要super(props),不然拿不到props
  this.props = props;
}

// super也让子类可以拿到thie.setState
setState(state) {
  this.state = Object.assign({}, this.state, state);

  let newVDOM = this.render();     // 最新的要渲染的 newVDOM 对象
  let oldDOM = this.getDOM();      // 旧的 virtualDOM 对象 进行比对
  let container = oldDOM.parentNode;

  diff(newVDOM, container, oldDOM);
}

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

如果子类没有定义constructor方法,这个方法会被默认添加。

class A extends B {
}

// 等同于
class A extends B {
  constructor(...args) {
    super(...args);
  }
}

React.forwardRef是什么?它有什么作用?

React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:

  • 转发 refs 到 DOM 组件
  • 在高阶组件中转发 refs

类组件与函数组件有什么异同

相同点

  • 组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。

不同点

  • 编程思想:类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。
  • 使用场景:之前,在使用场景上,如果存在需要使用生命周期的组件,那么主推类组件;设计模式上,如果需要使用继承,那么主推类组件。但现在由于 React Hooks 的推出,生命周期概念的淡出,函数组件可以完全取代类组件。其次继承并不是组件最佳的设计模式,官方更推崇“组合优于继承”的设计概念,所以类组件在这方面的优势也在淡出。
  • 类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的未来发展。

state和props有什么区别

  • props 是传递给组件的(类似于函数的形参),而state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。
  • props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改,这体现了React浓重的函数式编程的思想。
  • state 是在组件中创建的,一般在 constructor中初始化 state。state 是多变的、可以修改,每次setState都异步更新的。

生命周期

  • 装载阶段(Mount),组件第一次在DOM树中被渲染的过程
    • constructor: 一般就做两件事:
      • 初始化组件的 state
      • 给事件处理方法绑定 this
    • static getDerivedStateFromProps(props, state): 用来同步prop和state
    • render: 这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑。
    • componentDidMount: 在组件挂载后(插入 DOM 树中)立即调用,主要用来:
      • 执行依赖于DOM的操作;
      • 发送网络请求(官方建议)
      • 添加订阅消息(会在componentWillUnmount取消订阅)
  • 更新过程(Update),组件状态发生变化,重新更新渲染的过程
    • getDerivedStateFromProps
    • shouldComponentUpdate(nextProps, nextState)
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate: 会在更新后会被立即调用,首次渲染不会执行此方法。
      • 当组件更新后,对 DOM 进行操作;
      • 如果对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
  • 卸载过程(Unmount),组件从DOM树中被移除的过程
    • componentWillUnmount: 会在组件卸载及销毁之前直接调用, 用来执行清理:
      • 清除 timer,取消网络请求或清除
      • 取消在 componentDidMount() 中创建的订阅等;

注意 如果在 componentDidMount 中调用 setState ,就会触发一次额外的渲染,多调用了一次 render 函数,由于它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,但是我应当避免这样使用,这样会带来一定的性能问题,尽量是在 constructor 中初始化 state 对象。

UNSAFE的生命周期

React16中depreacate了这三个

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

这里的UNSAFE并不是指安全性,而是表示两个事情:

  • 使用这些生命周期的代码将更有可能在未来的React版本中存在缺陷,特别是一旦启用了异步渲染。因为fiber的出现,很可能因为高优先级任务的出现而打断现有任务导致它们会被执行多次。
  • 新手容易用法不标准而造成一些问题。React想约束使用者,好的框架能够让人不得已写出容易维护和扩展的代码

componentWillMount

componentWillMount生命周期发生在首次渲染前,一般在这里初始化数据或异步获取外部数据赋值。

问题 假如组件在第一次渲染的时候被中断,由于组件没有完成渲染,所以并不会执行componentWillUnmount生命周期(componentWillUnmount必须在componenDidMount以后才能被调用),但是componentWillMount里面执行的subscribe就无法被unsubscribe。

官方推介 这个函数的功能完全可以使用componentDidMount和 constructor来代替

  • 初始化数据和subscribe,react官方建议放在constructor里面
  • 而异步获取外部数据,放在componentDidMount里面

componentWillReceiveProps

这个一般是用来同步state和prop。

问题

  • componentWilReceiveProps 中判断前后两个 props 是否相同,如果不同再将新的 props更新到相应的 state 上去。这样做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,另一方面也会增加组件的重绘次数。
  • 如果用的不规范的话,比如在这里面调用了父组件的updateXXX,可能导致无限循环。
  • 因为fiber,可能被调用多次

官方推介 使用getDerivedStateFromProps,这是静态方法,在这里不能使用this,也是一个纯函数,开发者不能写出副作用的代码

componentWillUpdate

componentWillUpdate在视图更新前触发,一般用于视图更新前保存一些数据方便视图更新完成后赋值,比如列表加载更新后回到当前滚动条位置。

问题

  • 因为fiber,可能被调用多次。或者在需要获取DOM元素状态时,但是由于在fber中,render可打断,可能在wilMount中获取到的元素状态很可能与实际需要的不同
  • 由于componentWillUpdate和componentDidUpdate这两个生命周期函数有一定的时间差,会导致一些性能问题。

官方推介 使用getSnapshotBeforeUpdate。会在最终确定的render执行之前执行:

  • 能保证其获取到的元素状态与didUpdate中获取到的元素状态相同
  • 保证只执行一次

state 和 props 触发更新的生命周期分别有什么区别

props更新流程其实就在最开始多了一个componentWillReceiveProps。

state更新流程:

  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

props更新流程:

  • componentWillReceiveProps
  • 跟上面一样的四步流程

组件通信

  • 父子组件的通信方式
    • 用prop
  • 子组件向父组件通信
    • props传一个回调给子
  • 跨级组件的通信方式
    • 多层传prop
    • 用context
  • 兄弟组件通信
    • 找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信
  • 非嵌套关系组件的通信方式
    • 可以通过redux等进行全局状态管理
    • 可以使用自定义事件通信(发布订阅模式)

React-Router

实现原理

基于 hash 的路由: HashRouter
使用 URL 的 hash 部分(即 window.location.hash)来保持 UI 和 URL 的同步,通过监听hashchange事件,感知 hash 的变化。hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。

改变 hash 可以直接通过 location.hash=xxx

基于 H5 history 路由: BrowserRouter
它使用 HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 和 URL 的同步。监听 url 的变化可以通过自定义事件触发实现。

改变 url 可以通过 history.pushState 和 resplaceState 等,会将URL压入堆栈,同时能够应用 history.go() 等 API。而且不会刷新界面。

虽然美观,但是刷新会出现 404 需要后端进行配置

使用

Switch 不是分组 Route 所必须的,但他通常很有用。 一个 Switch 会遍历其所有的子 Route元素,并仅渲染与当前地址匹配的第一个元素。

<Switch>
  <Route exact path="/" component={ Home } />
  <Route path="/about" component={ About } />
  <Route path="/contact" component={ Contact } />
</Switch>
<Link to="/">Home</Link>   
<Redirect to="/">Home</Redirect>   
<NavLink to="/react" activeClassName="hurray">React</NavLink>

设置重定向

<Switch>
  <Redirect from='/users/:id' to='/users/profile/:id'/>
  <Route path='/users/profile/:id' component={Profile}/>
</Switch>

Link 标签和 a 标签

虽然最终都是渲染成a标签,但用Link的时候,react-router接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的“跳转”行为只会触发相匹配的<Route>对应的页面内容更新,而不会刷新整个页面。

Link做了三件事:

  • 有onclick那就执行onclick
  • click的时候阻止a标签默认事件
  • 根据跳转href(即是to),用history或hash跳转,此时只是链接变了,并没有刷新页面。而<a>标签就是普通的超链接了,会刷新界面。

a标签默认事件禁掉之后做了什么才实现了跳转?

document.getElementsByTagName('a').forEach(item => {
  item.addEventListener('click', function () {
    location.href = this.href
  })
})

如何获取URL的参数和历史对象

  • get传值:比如admin?id=1111search就是问号后面的值,可以用this.props.location.search
  • 动态路由传值:如path='/admin/:id',可以用this.props.match.params.id,或者useParams hook。
  • 通过query或state传值:Link组件的to属性中可以传递对象{ pathname:'/admin',query:'111',state:'111' },可以通过this.props.location.statethis.props.location.query
  • 获取历史对象: useHistory()或者this.props.history

React 数据持久化有什么实践吗

自己包装一个storage

let storage={
  set(key, value){
    localStorage.setItem(key, JSON.stringify(value));
  },
  get(key){
    return JSON.parse(localStorage.getItem(key));
  },
  remove(key){
    localStorage.removeItem(key);
  }
};

在React项目中,通过redux存储全局数据时,会有一个问题,如果用户刷新了网页,那么通过redux存储的全局数据就会被全部清空,比如登录信息等。这时就会有全局数据持久化存储的需求。首先想到的就是localStorage,localStorage是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。

但是在已经使用redux来管理和存储全局数据的基础上,再去使用localStorage来读写数据,这样不仅是工作量巨大,还容易出错。那么有没有结合redux来达到持久数据存储功能的框架呢?当然,它就是redux-persist。

还有几个数据持久化的方案:

  • sessionStorge: componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据
  • history API: History API 的 pushState 函数可以给历史记录关联一个任意的可序列化 state,所以可以在路由 push 的时候将当前页面的一些信息存到 state 中,下次返回到这个页面的时候就能从 state 里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。

React必须使用JSX吗

每个 JSX 元素只是React.createElement(component, props, ...children) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。所以React 并不强制要求使用 JSX,但是会更加方便。

因为,就算没有直接使用react,也需要引入react。(react 17以后babel会帮我们自动引入)

React.Children.map和js的map有什么区别

JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。

React.Children.map还可以拿到child的各种属性。

React 设计思路,它的理念是什么

  • 组件化思想,函数式编程。每个组件就是一个函数,UI = f(data)
  • virtual DOM,不要直接操作DOM
  • 单向数据流

React的严格模式如何使用,有什么用处

与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。 可以为应用程序的任何部分启用严格模式

function ExampleApplication() {
  // 不会对 Header 和 Footer 组件运行严格模式检查。但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查。
  return (
    <div>
      <Header />
      <React.StrictMode>        
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>      
      <Footer />
    </div>
  );
}

MVC框架的主要问题是什么?

  • 对 DOM 操作的代价非常高
  • 代码耦合严重,由于循环依赖性,组件模型需要围绕 models 和 views 进行创建

references