Bootstrap

React 灵魂 23 问

1、setState 是异步还是同步?

相关链接:

2、聊聊 react@16.4 + 的生命周期

相关连接:

3、useEffect(fn, []) 和 componentDidMount 有什么差异?

会捕获 和 。所以即便在回调函数里,你拿到的还是初始的 和 。如果想得到“最新”的值,可以使用 。

4、hooks 为什么不能放在条件判断里?

以 为例,在 react 内部,每个组件(Fiber)的 hooks 都是以链表的形式存在 属性中:

update 阶段,每次调用 ,链表就会执行 next 向后移动一步。如果将 写在条件判断中,假设条件判断不成立,没有执行里面的 方法,会导致接下来所有的 的取值出现偏移,从而导致异常发生。

参考链接:

5、fiber 是什么?

React Fiber 是一种基于浏览器的单线程调度算法。

React Fiber 用类似 的机制来做异步 diff。但是之前数据结构不支持这样的实现异步 diff,于是 React 实现了一个类似链表的数据结构,将原来的 递归diff 变成了现在的 遍历diff,这样就能做到异步可更新了。

相关链接:

6、聊一聊 diff 算法

传统 diff 算法的时间复杂度是 O(n^3),这在前端 render 中是不可接受的。为了降低时间复杂度,react 的 diff 算法做了一些妥协,放弃了最优解,最终将时间复杂度降低到了 O(n)。

那么 react diff 算法做了哪些妥协呢?,参考如下:

1、tree diff:只对比同一层的 dom 节点,忽略 dom 节点的跨层级移动

如下图,react 只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点不存在时,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。

这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

这就意味着,如果 dom 节点发生了跨层级移动,react 会删除旧的节点,生成新的节点,而不会复用。

2、component diff:如果不是同一类型的组件,会删除旧的组件,创建新的组件

3、element diff:对于同一层级的一组子节点,需要通过唯一 id 进行来区分

如果没有 id 来进行区分,一旦有插入动作,会导致插入位置之后的列表全部重新渲染。

这也是为什么渲染列表时为什么要使用唯一的 key。

7、调用 setState 之后发生了什么?

8、为什么虚拟dom 会提高性能?

虚拟dom 相当于在 JS 和真实 dom 中间加了一个缓存,利用 diff 算法避免了没有必要的 dom 操作,从而提高性能。

9、错误边界是什么?它有什么用?

在 React 中,如果任何一个组件发生错误,它将破坏整个组件树,导致整页白屏。这时候我们可以用错误边界优雅地降级处理这些错误。

例如下面封装的组件:

class ErrorBoundary extends React.Component {
  constructor(props: IProps) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 可以将错误日志上报给服务器
    console.log('组件奔溃 Error', error);
    console.log('组件奔溃 Info', errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return this.props.content;
    }
    return this.props.children;
  }
}

10、什么是 Portals?

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

ReactDOM.createPortal(child, container)

11、React 组件间有那些通信方式?

父组件向子组件通信

1、 通过 props 传递

子组件向父组件通信

1、 主动调用通过 props 传过来的方法,并将想要传递的信息,作为参数,传递到父组件的作用域中

跨层级通信

1、 使用 react 自带的 进行通信, 创建上下文, 使用上下文。

参考下面代码:

import React, { createContext, useContext } from 'react';

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = createContext(themes.light);

function App() {
  return (
    
      
    
  );
}

function Toolbar() {
  return (
    
); } function ThemedButton() { const theme = useContext(ThemeContext); return ( ); } export default App;

2、使用 Redux 或者 Mobx 等状态管理库

3、使用订阅发布模式

相关链接:

12、React 父组件如何调用子组件中的方法?

1、如果是在方法组件中调用子组件(),可以使用 和 :

const { forwardRef, useRef, useImperativeHandle } = React;

const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    getAlert() {
      alert("getAlert from Child");
    }
  }));
  return 

Hi

; }); const Parent = () => { const childRef = useRef(); return (
); };

2、如果是在类组件中调用子组件(),可以使用 :

const { Component } = React;

class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }

  onClick = () => {
    this.child.current.getAlert();
  };

  render() {
    return (
      
); } } class Child extends Component { getAlert() { alert('getAlert from Child'); } render() { return

Hello

; } }

参考阅读:

13、React有哪些优化性能的手段?

类组件中的优化手段

1、使用纯组件 作为基类。

2、使用 高阶函数包装组件。

3、使用 生命周期函数来自定义渲染逻辑。

方法组件中的优化手段

1、使用 。

2、使用 。

其他方式

1、在列表需要频繁变动时,使用唯一 id 作为 key,而不是数组下标。

2、必要时通过改变 CSS 样式隐藏显示组件,而不是通过条件判断显示隐藏组件。

3、使用 和 进行懒加载,例如:

import React, { lazy, Suspense } from "react";

export default class CallingLazyComponents extends React.Component {
  render() {
    var ComponentToLazyLoad = null;

    if (this.props.name == "Mayank") {
      ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
    } else if (this.props.name == "Anshul") {
      ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
    }

    return (
      

This is the Base User: {this.state.name}

Loading...
}>
) } }

用法可以参考

相关阅读:

14、为什么 React 元素有一个 $$typeof 属性?

目的是为了防止 XSS 攻击。因为 Synbol 无法被序列化,所以 React 可以通过有没有 $$typeof 属性来断出当前的 element 对象是从数据库来的还是自己生成的。

如果没有 $$typeof 这个属性,react 会拒绝处理该元素。

在 React 的古老版本中,下面的写法会出现 XSS 攻击:

// 服务端允许用户存储 JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {
      __html: '/* 把你想的搁着 */'
    },
  },
  // ...
};
let message = { text: expectedTextButGotJSON };

// React 0.13 中有风险

{message.text}

相关阅读:

15、React 如何区分 Class组件 和 Function组件?

一般的方式是借助 typeof 和 Function.prototype.toString 来判断当前是不是 class,如下:

function isClass(func) {
  return typeof func === 'function'
    && /^class\s/.test(Function.prototype.toString.call(func));
}

但是这个方式有它的局限性,因为如果用了 babel 等转换工具,将 class 写法全部转为 function 写法,上面的判断就会失效。

React 区分 Class组件 和 Function组件的方式很巧妙,由于所有的类组件都要继承 React.Component,所以只要判断原型链上是否有 React.Component 就可以了:

AComponent.prototype instanceof React.Component

相关阅读:

16、HTML 和 React 事件处理有什么区别?

在 HTML 中事件名必须小写:

而在 React 中需要遵循驼峰写法:

在 HTML 中可以返回 false 以阻止默认的行为:

而在 React 中必须地明确地调用 :

function handleClick(event) {
  event.preventDefault()
  console.log('The link was clicked.')
}

17、什么是 suspense 组件?

Suspense 让组件“等待”某个异步操作,直到该异步操作结束即可渲染。在下面例子中,两个组件都会等待异步 API 的返回值:

const resource = fetchProfileData();

function ProfilePage() {
  return (
    Loading profile...}>
      
      Loading posts...}>
        
      
    
  );
}

function ProfileDetails() {
  // 尝试读取用户信息,尽管该数据可能尚未加载
  const user = resource.user.read();
  return 

{user.name}

; } function ProfileTimeline() { // 尝试读取博文信息,尽管该部分数据可能尚未加载 const posts = resource.posts.read(); return (
    {posts.map(post => (
  • {post.text}
  • ))}
); }

Suspense 也可以用于懒加载,参考下面的代码:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    
Loading...
}>
); }

18、为什么 JSX 中的组件名要以大写字母开头?

因为 React 要知道当前渲染的是组件还是 HTML 元素。

19、redux 是什么?

Redux 是一个为 JavaScript 应用设计的,可预测的状态容器,其特点是。

它解决了如下问题:

  • 跨层级组件之间的数据传递变得很容易

  • 所有对状态的改变都需要 dispatch,使得整个数据的改变可追踪,方便排查问题。

但是它也有缺点:

  • 概念偏多,理解起来不容易

  • 样板代码太多

20、react-redux 的实现原理?

通过 redux 和 react context 配合使用,并借助高阶函数,实现了 。

参考链接:

21、reudx 和 mobx 的区别?

得益于 Mobx 的 observable,使用 mobx 可以做到精准更新;对应的 Redux 是用 dispath 进行广播,通过Provider 和 connect 来比对前后差别控制更新粒度;

相关阅读:

22、redux 异步中间件有什么什么作用?

假如有这样一个需求:请求数据前要向 Store dispatch 一个 loading 状态,并带上一些信息;请求结束后再向Store dispatch 一个 loaded 状态

一些同学可能会这样做:

function App() {
  const onClick = () => {
    dispatch({ type: 'LOADING', message: 'data is loading' })
    fetch('dataurl').then(() => {
      dispatch({ type: 'LOADED' })
    });
  }

  return (
); }

但是如果有非常多的地方用到这块逻辑,那应该怎么办?

聪明的同学会想到可以将 onClick 里的逻辑抽象出来复用,如下:

function fetchData(message: string) {
  return (dispatch) => {
    dispatch({ type: 'LOADING', message })
    setTimeout(() => {
      dispatch({ type: 'LOADED' })
    }, 1000)
  }
}

function App() {
  const onClick = () => {
    fetchData('data is loading')(dispatch)
  }

  return (
); }

很好,但是 这种写法有点奇怪,会增加开发者的心智负担。

于是可以借助 rudux 相关的异步中间件,以 为例,将写法改为如下:

function fetchData(message: string) {
  return (dispatch) => {
    dispatch({ type: 'LOADING', message })
    setTimeout(() => {
      dispatch({ type: 'LOADED' })
    }, 1000)
  }
}

function App() {
  const onClick = () => {
-   fetchData('data is loading')(dispatch)
+   dispatch(fetchData('data is loading'))
  }

  return (
); }

这样就更符合认知一些了,redux 异步中间件没有什么奥秘,主要做的就是这样的事情。

相关阅读:

23、redux 有哪些异步中间件?

1、redux-thunk

源代码简短优雅,上手简单

2、

借助 JS 的 generator 来处理异步,避免了回调的问题

3、

借助了 RxJS 流的思想以及其各种强大的操作符,来处理异步问题

**觉得不错可以点击这个 关注更多内容。**

关于我们

Customize this section to tell your visitors a little bit about your publication, writers, content, or something else entirely. Totally up to you.