Fork me on GitHub

React-面试

React

React 相关面试题

什么是 React

  • 说概念
  • 讲用途
  • 理思路
  • 列优缺点

React 是一个网页 UI 框架,通过组件化的方式解决了视图层开发复用的问题,本质上是一个组件化的框架。
核心设计思路:声明式、组件化、通用性
声明式的优势在于直观与组合
组件化的优势在于视图的拆分与模块复用,可以更容易做到高内聚低耦合
通用性在于一次学习,随处编写。比如 React Native、React360,主要靠虚拟 DOM 实现,这使 React 的适用性变得更广,无论是 Web、Native、VR 都可以进行开发。这也是 React 的优势。
但是作为视图层的框架,React 的劣势也十分明显。它没有提供一揽子的解决方案,在开发大型前端应用时,需要向社区寻找并整合解决方案。虽然一定程度上促进了社区的繁荣,但也为开发者在技术选项和学习使用上增加了成本。

为什么 React 要用 JSX

  • 一句话解释 JSX
  • 核心概念,JSX 用于解决什么问题,如何使用
  • 方案对比。与其他方案对比,说明 React 选用 JSX 的必要性

JSX 是一个JavaScript的语法扩展,结构类似 XML。
JSX 主要用于声明 React 元素。React 并不强制使用 JSX,JSX 是React.createElement()的语法糖。使用 JSX 的代码更为简洁,结构更为清晰。
React 采用了关注点分离的设计原则,关注点的基本单位是组件。
首先,对比模板,模板不应该是开发过程的关注点,因为引入模板语法、模板指令等概念。是一种不佳的实现方案。
其次,对比模板字符串,模板字符串编写会造成多次内部嵌套,使整个结构变得复杂,并且优化代码提示也会变得困难。
最后 JXON,也是因为代码提示困难的原因被放弃。

生命周期

挂载

  • constructor
    初始化函数,不属于声明周期,不推荐使用。
  • static getDerivedStateFromProps
    静态方法,在 props 变化的时候更新 state 或直接复制 prop 到 state。父组件传入的 props 发生变化,或者父组件重新渲染,都会执行。
    使用场景优先不建议使用。
  • UNSAFF_componentWillMount
    已标记废弃,在 React 的异步渲染机制下,可能多次调用。
  • render
    返回 JSX 结构,用于描述具体的渲染内容。render 函数并没有真正的渲染组件,渲染是依靠 React 操作 JSX 描述结构来完成的。render 应该是个纯函数,不应该产生副作用,比如调用 seState 或绑定事件。
    调用 setState 会在每次渲染的时候再次触发渲染,导致死循环。
    绑定事件会被频繁调用注册。
  • componentDidMount
    主要用于组件加载完成后执行某些操作,发起网络请求或者绑定事件。在 render 函数调用后执行
    在浏览器端可以认为是真是 DOM 绘制完成后调用的。
    其他场景尤其是 React Native 场景下,由于机器的性能限制,视图绘制可能还在绘制中。

更新阶段

  • UNSAFE_componentWillReceiveProps
    该函数已被标记弃用,因为其功能可被函数 getDerivedStateFromProps 所替代。
    另外,当 getDerivedStateFromProps 存在时,UNSAFE_componentWillReceiveProps 不会被调用。
  • static getDerivedStateFromProps
    同挂载阶段的表现一致。
  • shouldComponentUpdate
    通过返回 true 或 false 来确定是否需要触发渲染。通过添加判断条件来阻止不必要的渲染。
    React 的 PureComponent 通过实现默认的 shouldComponentUpdate 函数来优化渲染,通过对 props 和 state 的浅比较来决定是否触发更新。
  • UNSAFE_componentWillUpdate
    同样已废弃,因为后续的 React 异步渲染设计中,可能会出现组件暂停更新渲染的情况。
  • render
    同挂载阶段一致
  • getSnapshotBeforeUpdate
    配合 React 新的异步渲染的机制,在 DOM 更新发生前被调用,返回值将作为 componentDidUpdate 的第三个参数。
    不可以执行 setState
  • componentDidUpdate
    可以使用 setState,会触发重渲染,但一定要小心使用,避免死循环。

卸载阶段

  • componentWillUnmount
    清理定时器,解除事件绑定

错误边界

  • static getDerivedStateFromError
  • componentDidCatch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }
  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children; 
  }
}

容易造成生命周期的坑

  1. getDerivedStateFromProps容易编写反模式代码,是受控组件与非受控组件区分模糊
  2. componentWillMount,已废弃,新的异步更新模式会导致重复渲染
  3. componentWillReceiveProps 同样被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。
  4. shouldComponentUpdate 通过返回 true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。
  5. componentWillUpdate 同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合 getSnapshotBeforeUpdate 与 componentDidUpdate 改造使用。
  6. componentWillUnmount,如果在 componentWillUnmount 函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug。
  7. 如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加。

React 的请求应该放在哪里,为什么

对于异步请求,放在 componentDidMount 中。
从时间顺序上还可以有以下选择:

  • constructor:可以放,但是设计上不推荐。负责初始化工作,不应该涉及业务逻辑,另外随着类属性的流行,很少使用了。
  • componentWillMount:已经被标记废弃。在新的异步渲染模式下会导致重复渲染引发 bug,不利于维护

React 组件设计

  1. 设计分类
    • 展示组件 专注于组件本身特性
      • 代理组件
        用于封装属性、减少代码重复,例如对 Antd 的 Button 自己封装一层,可以统一为 Button 添加属性,如果需要替换掉 Antd 也很方便。
      • 样式组件
      • 布局组件
    • 灵巧组件 专注于组合组件
      • 容器组件 拉取数据与组合组件
      • 高阶组件
        • 逻辑复用
        • 链式复用
        • 渲染劫持 super.render
        • 缺陷
          • 丢失静态函数,hoist-non-react-statics
          • refs 属性不能透传 React.forwardRef
  2. 工程实践
    • 目录结构划分
    • 引入工程管理

如何在渲染劫持中为原本的渲染结果添加新的样式?

1
2
3
4
5
6
7
8
9
10
11
function withLoading(WrappedComponent) {
return class extends WrappedComponent {
render() {
if(this.props.isLoading) {
return <Loading />;
} else {
return super.render();//获取原本的渲染结果,修改
}
}
};
}

类组件和函数组件

  1. 相同点
    1. 实现效果一致
    2. 使用方式一致
  2. 不同点
    1. 设计实现和心智模型不同
      1. 类组件 OOP(面向对象编程)
      2. 函数组件 FP 函数式编程
    2. 生命周期
      类组件通过生命周期包裹业务逻辑,函数组件早期通过高阶函数模拟声明周期,现在使用 Hooks 实现
    3. 设计模式
      类组件可以实现继承 函数组件不行,但是 React 推荐组合优于继承
    4. 性能优化
      • 类组件 PureComponentshouldComponentUpdate
      • 函数组件 React.memouseCallback
  3. 未来趋势
    由于生命周期的复杂度,并且不易于优化,函数组件本身比较轻量且符合 React 的View=fn(state,props)的设计理念,并且 Hooks 提供了比原先更加合理的逻辑处理策略。

hooks 理解

  1. useRef,保存 dom 元素,获取
    保持引用不变,jsx 渲染比 useEffect 早,手动修改 ref.current 不会触发组件的重新渲染
  2. useMemo 存储一个与渲染无关的需要大量计算的不常改变的值。
  3. useCallback 保证渲染前后的函数不会重新生成,避免子组件不必要的渲染,必须和 React.memo 配合使用
-------------本文结束感谢阅读-------------