Skip to content

React 进阶

一: React Hooks 是什么,有哪些常用的 Hook?

React Hooks 是 React 16.8 引入的一种新的特性,它允许函数组件中使用状态(State)和其他 React 特性,使得函数组件可以拥有类组件相似的能力。Hooks 是一组可以在函数组件中“钩入” React 特性的函数。

以下是一些常用的 React Hooks:

  1. useState:
    • 用于在函数组件中添加局部状态。
    • 返回一个包含当前状态值和更新状态值的数组。
jsx
import React, { useState } from 'react';

const ExampleComponent = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};
  1. useEffect:
    • 用于在函数组件中执行副作用操作,比如数据获取、订阅、手动操作 DOM 等。
    • 接收一个函数,该函数可以返回一个清理函数。
jsx
import React, { useState, useEffect } from 'react';

const ExampleComponent = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 执行副作用操作,比如数据获取
    fetchData().then((result) => {
      setData(result);
    });

    // 返回清理函数(可选)
    return () => {
      // 清理操作,比如取消订阅
    };
  }, []); // 依赖数组为空时,仅在组件挂载和卸载时执行
};
  1. useContext:
    • 用于在函数组件中读取 React Context 的值。
jsx
import React, { useContext } from 'react';
import MyContext from './MyContext';

const ExampleComponent = () => {
  const contextValue = useContext(MyContext);

  return <p>Context value: {contextValue}</p>;
};
  1. useReducer:
    • 用于在函数组件中管理复杂的状态逻辑。
    • 接收一个状态处理函数和初始状态。
jsx
import React, { useReducer } from 'react';

const initialState = { count: 0 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    default:
      return state;
  }
};

const ExampleComponent = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
    </div>
  );
};
  1. useCallback 和 useMemo:
    • useCallback 用于记忆一个回调函数,避免在每次渲染时创建新的函数。
    • useMemo 用于记忆计算结果,避免在每次渲染时重新计算。
jsx
import React, { useCallback, useMemo } from 'react';

const ExampleComponent = () => {
  const memoizedCallback = useCallback(() => {
    // 回调函数逻辑
  }, [/* 依赖项 */]);

  const memoizedValue = useMemo(() => {
    // 计算结果逻辑
    return /* 计算结果 */;
  }, [/* 依赖项 */]);

  return (
    <div>
      <p>Callback result: {memoizedCallback()}</p>
      <p>Computed value: {memoizedValue}</p>
    </div>
  );
};

这些是一些常用的 React Hooks,它们使得函数组件能够更方便地管理状态、副作用等,取代了之前类组件中的一些生命周期方法和状态管理逻辑。在函数组件中使用 Hooks 可以使代码更简洁、易读,并且更容易实现复杂的逻辑。

二: React 中的 Context 是用来做什么的?

React 中的 Context 用于在组件树中共享某些值,而不必一级一级手动地传递这些值。通过 Context,可以将值沿着组件树传递给任何深度的组件。

Context 主要用于以下场景:

  1. 全局主题设置:
    • 在整个应用中共享主题配置,例如颜色、字体等。
jsx
// 创建 Context
const ThemeContext = React.createContext('light');

// 提供 Context 的父组件
const ThemeProvider = ({ children }) => {
  const theme = 'dark';

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
};

// 子组件中消费 Context
const ThemedComponent = () => {
  const theme = useContext(ThemeContext);

  return <p>Current theme: {theme}</p>;
};

// 在应用中使用 Context
const App = () => {
  return (
    <ThemeProvider>
      <ThemedComponent />
    </ThemeProvider>
  );
};
  1. 用户认证状态:
    • 全局共享用户认证状态,以便在整个应用中检查用户是否已登录。
jsx
// 创建 Context
const AuthContext = React.createContext(false);

// 提供 Context 的父组件
const AuthProvider = ({ children }) => {
  const isAuthenticated = true;

  return (
    <AuthContext.Provider value={isAuthenticated}>
      {children}
    </AuthContext.Provider>
  );
};

// 子组件中消费 Context
const AuthenticatedComponent = () => {
  const isAuthenticated = useContext(AuthContext);

  return isAuthenticated ? <p>Welcome, user!</p> : <p>Please log in</p>;
};

// 在应用中使用 Context
const App = () => {
  return (
    <AuthProvider>
      <AuthenticatedComponent />
    </AuthProvider>
  );
};
  1. 多语言支持:
    • 在整个应用中共享当前语言配置。
jsx
// 创建 Context
const LanguageContext = React.createContext('en');

// 提供 Context 的父组件
const LanguageProvider = ({ children }) => {
  const language = 'fr';

  return (
    <LanguageContext.Provider value={language}>
      {children}
    </LanguageContext.Provider>
  );
};

// 子组件中消费 Context
const TranslatedComponent = () => {
  const language = useContext(LanguageContext);

  return (
    <p>
      {language === 'en' ? 'Hello' : 'Bonjour'}
    </p>
  );
};

// 在应用中使用 Context
const App = () => {
  return (
    <LanguageProvider>
      <TranslatedComponent />
    </LanguageProvider>
  );
};

Context 提供了一种在组件树中传递数据的方式,可以避免层层传递属性的繁琐操作,使得共享值更加简便和方便。

三: React 中的高阶组件(HOC)是什么,有什么使用场景?

高阶组件(Higher-Order Component,HOC)是一个函数,它接受一个组件并返回一个新的组件。HOC 是 React 中一种用于复用组件逻辑的高级技术。通过将组件包裹在 HOC 中,可以在不修改原始组件代码的情况下,向组件添加新的功能。

HOC 的主要特点包括:

  1. 接受组件作为参数: HOC 接受一个组件作为参数,并返回一个新的增强版组件。

  2. 返回新组件: HOC 返回的组件通常会包含一些新的 props 或对组件的行为进行修改。

使用场景:

  1. 代码复用: 可以使用 HOC 将一些通用的功能逻辑抽离出来,形成可复用的组件逻辑。

  2. 状态抽象: HOC 可以用于将一些组件状态抽象为可复用的逻辑,使得多个组件可以共享相同的状态管理。

  3. 条件渲染: HOC 可以根据某些条件来决定是否渲染包裹的组件,实现条件渲染的逻辑。

  4. 监听生命周期事件: HOC 可以用于监听组件的生命周期事件,执行一些逻辑。

示例:

jsx
// 高阶组件示例:添加日志记录功能
const withLogger = (WrappedComponent) => {
  return class WithLogger extends React.Component {
    componentDidMount() {
      console.log(`Component ${WrappedComponent.name} is mounted`);
    }

    componentWillUnmount() {
      console.log(`Component ${WrappedComponent.name} is unmounted`);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
};

// 使用高阶组件
const EnhancedComponent = withLogger(MyComponent);

在上述示例中,withLogger 是一个高阶组件,它接受一个组件 MyComponent 作为参数,并返回一个新的组件 EnhancedComponent,这个新组件具有在组件挂载和卸载时输出日志的功能。

需要注意的是,虽然 HOC 是一种强大的模式,但在 React 16.3 版本后,使用 React Hooks 也提供了一种更灵活和易于理解的方式来共享组件逻辑。因此,在选择使用 HOC 时,可以考虑是否有更适合的替代方案。

四: React 中的渲染 Props 和渲染属性(Render Props)模式是什么?

在 React 中,渲染 Props 和渲染属性(Render Props)模式是一种通过将一个函数作为 prop 传递给组件,使得组件可以在渲染过程中提供可供渲染的内容的技术。这种模式的核心思想是允许组件通过函数属性来接收其它组件或元素的渲染逻辑。

具体而言,渲染 Props 和渲染属性模式通常包含以下步骤:

  1. 组件接受一个函数 prop: 父组件通过 prop 将一个函数传递给子组件。

  2. 子组件调用该函数并传递一些参数: 子组件在适当的时机调用这个函数,传递一些参数,以便父组件可以使用这些参数进行渲染。

  3. 父组件使用函数返回的内容进行渲染: 父组件使用函数返回的内容来渲染自己的内容。

这种模式的典型应用场景包括共享组件逻辑、抽象可复用的行为、实现高阶组件等。

示例:

jsx
// 渲染属性模式示例
class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({ x: event.clientX, y: event.clientY });
  };

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

// 使用渲染属性模式的父组件
const RenderPropsExample = () => (
  <MouseTracker render={(mouse) => (
    <p>The mouse position is ({mouse.x}, {mouse.y})</p>
  )} />
);

在上述示例中,MouseTracker 组件通过 render prop 接受一个函数,这个函数返回一个元素,用于渲染父组件的内容。父组件 RenderPropsExample 利用这个特性将具体的渲染逻辑传递给 MouseTracker,实现了一种灵活的组件复用方式。

这种模式的优势在于可以提供更灵活的组件组合方式,使得组件之间的逻辑和渲染的关系更加松耦合。

五: React 中的协调算法是什么,与 Diff 算法有何关系?

React 中的协调算法(Reconciliation Algorithm)是一种用于确定如何高效更新虚拟 DOM 树以反映实际 DOM 树变化的算法。协调算法主要用于比较两个不同时间点的虚拟 DOM 树,找到最小的变更,以减少实际 DOM 操作的次数,提高性能。

协调算法与 Diff 算法有密切的关系。Diff 算法是协调算法的一部分,负责比较两个树的节点,并确定更新、插入或删除的操作。React 中的 Diff 算法基于以下几个原则:

  1. 同级比较: React 只会在同级节点进行比较,不会跨级比较。这意味着如果两个节点的类型不同,React 不会尝试更新第一个节点,而是销毁旧节点并创建新节点。

  2. 唯一 key: 在同级节点中,React 使用节点的 key 属性来标识节点的唯一性。这有助于 React 正确识别新旧节点之间的对应关系。

  3. 简单的类型优先: 如果节点的类型不同,React 会销毁旧节点并创建新节点。如果节点的类型相同,React 会比较节点的属性和子节点。

  4. 列表中的操作优化: 当更新一个列表时,React 会尽量保持列表中每个节点的稳定性,最小化移动和重新创建节点的操作。

协调算法的目标是通过最小的更新量来保持虚拟 DOM 树与实际 DOM 树的同步。这样可以减少实际 DOM 操作,提高性能,并确保用户界面的响应性。

需要注意的是,React 的协调算法和 Diff 算法的实现可能随着 React 版本的更新而有所改变,因此具体的细节可能会有所不同。

六: React Fiber 是什么,有什么作用?

React Fiber 是 React 中的一种重新实现的核心算法。它是一种用于实现协调算法的新的、可中断的、增量式的算法。React Fiber 的目标是提高 React 应用的性能和交互性,并且能够更好地支持异步渲染、增强协调算法等特性。

主要作用包括:

  1. 增量式更新: Fiber 可以实现增量式的更新,将更新分割成小的任务单元,从而可以更灵活地控制任务的执行顺序。这使得 React 应用在执行更新时可以更容易地响应用户交互,提高了应用的交互性。

  2. 可中断的任务: Fiber 允许在执行任务时中断,并在稍后继续执行。这对于支持异步渲染非常有用,可以确保主线程在执行其他高优先级的任务(如用户输入响应)时不会被阻塞。

  3. 更好的错误边界: Fiber 提供了更好的错误处理机制,可以更准确地捕获并报告组件中的错误,而不至于导致整个应用崩溃。

  4. 并发模式: Fiber 为 React 引入了并发模式,使得 React 应用能够更好地利用浏览器的并发能力,提高渲染性能。

  5. 更灵活的协调算法: Fiber 允许 React 在不同优先级之间进行任务切换,使得 React 可以更好地适应不同场景下的性能需求。

React Fiber 的引入是为了使 React 更好地适应现代 Web 应用中复杂的交互和性能需求。在 React 16 版本及以上,React Fiber 已经成为 React 的默认渲染引擎。

七: React 中的懒加载和 Suspense 是如何实现的?

React 中的懒加载和 Suspense 是为了更好地支持异步加载组件和数据的特性。

懒加载:

懒加载是通过使用 React.lazy 函数来实现的。React.lazy 允许你定义一个动态加载的组件。它接受一个函数,该函数返回一个 import() 调用的 Promise。当组件被渲染时,它会自动加载并渲染相应的组件。

示例:

jsx
const MyLazyComponent = React.lazy(() => import('./MyComponent'));

function MyComponentWrapper() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <MyLazyComponent />
    </React.Suspense>
  );
}

在上述示例中,MyLazyComponent 是一个懒加载的组件,它会在需要时才被加载,而不是在应用初始化时。

Suspense:

Suspense 是用于处理异步操作的一种方式,它可以包裹在懒加载组件的外部。Suspense 接受一个 fallback 属性,用于在懒加载组件加载时显示一个加载提示。

示例:

jsx
import React, { Suspense } from 'react';

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

function MyComponentWrapper() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MyLazyComponent />
    </Suspense>
  );
}

在上述示例中,Suspense 包裹了 MyLazyComponent,当 MyLazyComponent 还在加载中时,会显示 <div>Loading...</div>

这两个特性结合起来使得 React 应用可以更灵活地处理异步加载的组件,并在加载期间提供友好的用户界面。需要注意的是,懒加载和 Suspense 目前主要用于函数组件。

八: React 中的错误边界是什么,如何使用?

错误边界是 React 中一种用于处理组件运行时错误的机制。它是一种组件,可以捕获并打印在其子组件树任何地方抛出的 JavaScript 错误,并且可以展示降级UI而不是崩溃整个组件树。错误边界被引入以解决在组件树深层次嵌套中,一个组件的错误导致整个应用崩溃的问题。

要使用错误边界,需要定义一个继承自 React.Component 的类,并实现 static getDerivedStateFromErrorcomponentDidCatch 这两个生命周期方法。getDerivedStateFromError 用于在渲染阶段捕获错误并更新组件的状态,而 componentDidCatch 则用于在错误被捕获后执行一些副作用。

示例:

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

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

// 使用错误边界包裹可能出错的子组件
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

在上述示例中,ErrorBoundary 是一个错误边界组件,它会捕获 MyComponent 及其子组件抛出的错误。当错误发生时,它会更新状态并显示一个错误提示。使用错误边界时,需要确保其包裹的组件具备一定的独立性,以防错误边界自身也受到错误的影响。

需要注意的是,错误边界仅捕获组件树中在其下方的组件内部抛出的错误,并不会捕获在事件处理、异步代码、服务端渲染、自身抛出的错误以及在错误边界内部抛出的错误(即在 render 方法内)等情况。