React面试(未完待续。。)

139 阅读6分钟

一、React18有哪些更新

1. 并发模式

批量更新

先复习下DOM2事件流,分为三个阶段:捕获阶段、目标阶段、冒泡阶段

image.png

冒泡是最容易根据名字记住的事件,从下往上,事件在父元素触发

冒泡常见应用场景也就是事件委托,将事件绑定在父元素上,点击子元素时,冒泡到父元素触发相应事件,这也是一种性能优化

React的批量更新也是基于这个原理,下面是一个demo

我们在同一个函数中设置了两次组件更新,但是实际上组件只会更新一次

import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';

// 示例组件
function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const handleClick = () => {
    // 同时更新两个状态
    setCount(prevCount => prevCount + 1);
    setText('Updated');
    // 在 React 18 中,这两个状态更新会被批量处理
  };

  console.log('Component Rendered'); // 用于观察组件的重新渲染

  return (
    <div>
      <button onClick={handleClick}>Update</button>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
    </div>
  );
}

// 使用 React 18 的 createRoot 进行渲染
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

大致流程:

  • 1.执行点击事件,react有一个标识函数是isBatchingUpdates,初始值为false
  • 2.遇上useState,isBatchingUpdates置为false,组件处于待更新状态
  • 3.再次遇到useState,由于isBatchingUpdates为true,所以将这个更新再次推入队列,合并更新
  • 4.函数执行完毕后,又会把isBatchingUpdates的值设为 false。然后开始执行整个组件的生命周期
  • 5.执行生命周期componentShouldUpdate,根据返回值判断是否要继续更新。
  • 6.执行生命周期componentWillUpdate
  • 7.执行真正的更新,render
  • 8.执行生命周期componentDidUpdate

二、React Hooks 是什么

react 自带hooks

最最常用的几个hooks,应该是useStateuseEffectuseContext等等

hooks 其实就是可复用的函数添加了state和生命周期,hook(钩子)这个词汇就是说在特定的时期组件被钩住了然后开始执行函数的逻辑。

常用hooks:

1.useState

  • 组件初始化时:当组件第一次渲染时,useState 使用传入的初始值(或初始函数的返回值)来设置状态变量的初始值。

  • 每次重新渲染时:每次组件重新渲染时,useState 都会返回当前的状态值和更新状态的函数。重要的是,useState 不会再次使用初始值,而是保持和返回之前保存的状态值。

2. useEffect

  • 初始渲染和更新

    • 当组件首次渲染时,useEffect 中的回调函数会执行。
    • 如果依赖项数组中的值发生变化,useEffect 中的回调函数也会执行。
  • 清理副作用

    • 在回调函数中返回一个清理函数,用于清理副作用(例如清除计时器、取消订阅等)。
    • 清理函数会在组件卸载时执行,或者在依赖项发生变化前执行。
  • 依赖项数组

    • 空数组 []useEffect 只在组件首次渲染和卸载时执行。
    • 不传依赖项数组:useEffect 在每次组件渲染时都会执行。
    • 有依赖项数组 [dependency1, dependency2, ...]useEffect 只在依赖项变化时执行。

3.useContext

  • useContext 用于在函数组件中访问上下文值。

  • 创建上下文对象并使用 Provider 提供上下文值。

  • 使用 useContext 获取上下文值,实现数据在组件树中的共享。

  • 上下文可以动态更新、在多个组件中使用,以及嵌套使用。

手动封装hooks

demo

useForm

import { useState } from 'react';

function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setValues({
      ...values,
      [name]: value,
    });
  };

  const resetForm = () => setValues(initialValues);

  return { values, handleChange, resetForm };
}

export default useForm

调用useForm

import React from 'react';
import useForm from './useForm';

function FormComponent() {
  const { values, handleChange, resetForm } = useForm({ username: '', email: '' });

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log(values);
    resetForm();
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          用户名:
          <input type="text" name="username" value={values.username} onChange={handleChange} />
        </label>
      </div>
      <div>
        <label>
          电子邮件:
          <input type="email" name="email" value={values.email} onChange={handleChange} />
        </label>
      </div>
      <button type="submit">提交</button>
    </form>
  );
}

export default FormComponent;

这个就是个比较经典的hook,我们把state封装进一个函数,通过传入不同的参数,更新state,然后执行相同的逻辑,得到参数对应的结果

三、React高阶组件

接收一个组件并返回一个新组件

HOC demo

高阶组件

import React, { useState, useEffect, ComponentType } from 'react';

// 定义 Props 类型,包含一个 `isLoading` 属性
interface WithLoadingProps {
  isLoading: boolean;
}

// 定义高阶组件,接收一个组件作为参数
function withLoading<P>(WrappedComponent: ComponentType<P>) {
  // 返回一个新的组件
  return function WithLoadingComponent(props: P & WithLoadingProps) {
    const { isLoading, ...restProps } = props;

    if (isLoading) {
      return <div>Loading...</div>;
    }

    return <WrappedComponent {...(restProps as P)} />;
  };
}

export default withLoading;

使用高阶组件增强普通组件

import React, { useState, useEffect } from 'react';
import withLoading from './withLoading';
import MyComponent from './MyComponent';

// 使用高阶组件增强 MyComponent
const MyComponentWithLoading = withLoading(MyComponent);

const EnhancedComponent: React.FC = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState<string>('');

  useEffect(() => {
    // 模拟数据获取
    setTimeout(() => {
      setData('Here is the fetched data');
      setIsLoading(false);
    }, 2000);
  }, []);

  return <MyComponentWithLoading isLoading={isLoading} data={data} />;
};

export default EnhancedComponent;

四、Redux

状态管理组件

这图很形象

image.png

在React中使用redux,官方建议安装两个其他插件 - Redux Toolkit 和 React-Redux

  1. Redux Toolkit(RTK):官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
  2. React-Redux :用来 链接 Redux 和 React 组件的中间件

image.png 着重讲讲React-Redux

通俗来讲,React-Redux将所有组件分成两大类:UI 组件和容器组件。

  1. UI组件:负责呈现页面。(React)
  2. 容器组件:负责管理数据和业务逻辑。(Redux)

看到这里都能猜出来,redux帮我们写了一些逻辑,在需要redux时,我们只要使用它提供的组件就可以简化代码数量和优化代码质量

Provider

这就是我们前面提到的高阶组件,也是redux帮我们提高效率的一个组件

作用: 将redux中的store传递给整个react应用程序,使所有组件都能访问到redux的状态,通过Provider,我们可以在react的任何地方使用redux的状态和派发动作。

使用方法:

  1. 创建多个reducer
// reducers/counterReducer.js
const initialState = {
  count: 0
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

export default counterReducer;

第二个reducer略

  1. 组合reducer
// reducers/index.js
import { combineReducers } from 'redux';
import counterReducer from './counterReducer';
import userReducer from './userReducer';

const rootReducer = combineReducers({
  counter: counterReducer,
  user: userReducer
});

export default rootReducer;
  1. 创建 Store
// store.js
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);

export default store;
  1. 设置Provider
// index.js 或 App.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
  1. 连接组件
// Counter.js
import React from 'react';
import { connect } from 'react-redux';

const Counter = ({ count, increment, decrement }) => (
  <div>
    <h1>{count}</h1>
    <button onClick={increment}>Increment</button>
    <button onClick={decrement}>Decrement</button>
  </div>
);

const mapStateToProps = (state) => ({
  count: state.counter.count
});

const mapDispatchToProps = (dispatch) => ({
  increment: () => dispatch({ type: 'INCREMENT' }),
  decrement: () => dispatch({ type: 'DECREMENT' })
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

6.使用组件

import React from 'react';
import Counter from './Counter';
import User from './User';

const App = () => (
  <div>
    <h1>React Redux Example</h1>
    <Counter />
    <User />
  </div>
);

export default App;

五、Fiber

听说这是react中比较高级的部分,但是代码示例比较少,暂且记录一笔,日后再理解

Fiber 的作用

虽然上述代码使用了 requestAnimationFrame 来实现逐步渲染,但在 React 内部,Fiber 架构通过以下方式实现类似的效果:

  1. 任务切片

    • React Fiber 将渲染任务分解为多个小任务,并在多个帧中逐步执行这些任务。这就类似于我们在代码中使用 requestAnimationFrame 来逐步添加项目。
  2. 优先级管理

    • React Fiber 为不同的更新任务分配不同的优先级。如果在渲染过程中出现高优先级的任务(如用户输入),React Fiber 可以中断当前的渲染任务,优先处理高优先级任务,然后再恢复之前的渲染任务。
  3. 可中断和恢复的渲染

    • React Fiber 的设计允许渲染过程可以被中断和恢复。这意味着在需要时,React 可以暂停当前的渲染任务,处理其他更重要的任务,然后再继续未完成的渲染任务。