react的笔记

102 阅读41分钟

react的知识点

1、react的所有hooks:

1、useState:

  • 用于在函数组件中添加状态。它返回一个由当前状态更新函数组成的数组

  • useState 以数组形式返回是为了方便使用ES6解构赋值语法来获取当前状态值更新状态的函数

  • useState的执行是同步的,而状态更新是异步的。状态更新会触发渲染函数。

  • 当调用 useState 返回的更新函数时,react 会将更新加入队列中,然后再适当的时机(通常在下一个渲染周期)执行队列中的更新操作。这个过程会触发组件重新渲染,从而更新组件中的 UI 以反映最新的状态

  • 每当状态更新时, React重新运行组件函数,并根据新的状态值生成新的虚拟 DOM ,然后与旧的虚拟 DOM 进行比较,并只更新发生变化的部分。这样就实现了局部更新,而不是整个页面重新渲染

2、useEffect:

  • 用于在组件渲染完成后执行副作用操作,如订阅、网络请求、DOM操作等。

  • useEffect 能模拟类组件的三个生命周期: componentDidMountcomponentDidUpdatecomponentWillUnmount

  • useEffect的常见用法:

    • 执行一次副作用
      // 执行一次副作用操作,相当于 componentDidMount
      useEffect(() => { 
          console.log('执行网络请求')
      }, [])
      // useEffect 的第二个参数是一个空数组 [],这表示副作用操作只会在组件的初始渲染完成后执行一次。
      
    • 订阅和取消订阅
      // 执行副作用操作,相当于 componentDidUpdate
      useEffect(() => {
          const subscription = someObservable.subscribe(handleChange);
      
          return () => {
              subscription.unsubscribe();
          };
      }, [someObservable])
      // useEffect 的回调函数订阅了一个可观察对象 someObservable 的更新,并且返回一个清理函数,用于取消订阅。当 someObservable 发生变化时,handleChange 函数会被调用。
      
    • 清理副作用
      useEffect(() => {
          const timer = setTnterval(() => {
              console.log('定时')
          }, 1000)
      
          return ()=> {
              // 清理副作用,相当于 componentWillUnmount
              clearInterVal(timer)
          }
      }, [])
      // useEffect 的回调函数设置了一个定时器,并返回一个清理函数,用于清理定时器。这可以确保在组件卸载或重渲染时清理副作用,防止内存泄漏或不必要的资源消耗。
      

3、useContext

  • 用于在函数组件中访问全局上下文数据。上下文是一种在组件树中共享数据的方式,它允许你传递数据组件树中的所有子组件。而不必,手动通过 props 一层层传递下去。useContext 可以轻松的在函数组件中访问上下文数据,而无需使用嵌套的 Context.Consumer 组件。

  • 全局状态管理

    • 可以将全局的状态数据存储在上下文中,并通过 useContext任意深度的组件访问更新这些状态。这样可以实现全局状态的管理。避免了 props driling (一层层转递 props )的问题。使得组件之间的数据共享更加方便和高效。
    • 可以通过 useContext 做:主题设置国际化用户身份认证等。
  • useContext会受影响因素:

    • 页面路由发生跳转时,整个 react应用 的组件树可能会重新渲染。如果使用 useContext 在多个组件中访问同一个上下文数据,那么在路由跳转时,所有访问这个上下文数据的组件都可能会重新渲染,从而重新获取上下文数据。这意味着路由跳转可能会导致上下文数据重新加载和组件的重新渲染

    • 页面刷新时,整个 react 应用状态都会被重置,包括上下文数据。这意味着使用 useContext 存储的上下文数据也会被重置,需要重新初始化。页面刷新相当于重新加载整个应用,因此上下文数据的状态也会被重新初始化

    • 在使用 useContext 来管理全局状态或其他共享数据的应用程序时,需要考虑页面路由跳转页面刷新对上下文数据的影响。通常情况下,上下文数据应该是持久性的,不受页面刷新或路由跳转的影响。可以将其存储在持久性的存储介质中,比如浏览器的本地存储 localStorage、会话存储sessionStorage。或者存到后端数据库中。

4、useReducer:

  • react 提供了一种更复杂的状态管理方式。它类似于 Reduxreducer,接受一个状态和一个操作,并返回新的状态

  • 基本用法:

    • reducer 是一个函数,接受俩个参数:当前状态 state 和分发的动作 action 。根据动作类型,reducer 函数返回新的状态。
    • initialState 是状态的初始化值。
    • state 是当前状态的值。
    • dispatch 是一个函数,用于分发动作。
          const [state, dispatch] = useReducer(reducer, initialState)
      
  • 创建 reducer 函数:

    • reducer 函数接收俩个参数:当前状态state 和分发的动作 action。根据动作的类型,在函数中更新状态并返回新的状态。reducer 函数通常使用 switch 语句来处理不同的动作类型。
      fucntion reducer(state, action) {
          switch (action.type) {
              case 'INCREMENT':
                  return { count: state.count + 1}
              case 'DECREMEN'
                  return { count: state.count - 1}
              default:
                  return state
          }
      }
      
  • 结合 useContext 进行全局状态管理:

    • 可以将 useReduceruseContext 结合使用,实现全局状态管理。首先创建一个上下文提供者,然后使用 useReducer 管理状态,并通过上下文共享状态分发函数
      const initalState = { count: 0 }
      export const CountContext = createContext();
      
      export const CountProvider = ({ children }) => {
          const [state, dispatch] = useReducer(reducer, initialState)
      
          return <CountContext.Provider value={{state, dispatch}}>
              { children }
          </CountContext.Provider>
      }
      
  • 优势

    • 可预测性:使用 reducer 函数管理状态可以更容易预测状态的变化,因为所有的状态变化集中一个地方
    • 可维护性:通过将状态逻辑分解成小的 reducer 函数,可以更容易的维护理解代码。
    • 可测试性reducer 函数是纯函数不包含副作用,因此更容易进行单元测试
  • useReducer 与 useState,俩者结合 useContext 来使用优劣势

    • useReducer 结合 useContext :

      • useReducer 提供了一种更复杂、更灵活状态管理方式,它允许你使用 reducer 函数来管理状态,并通过分发动作更新状态。这种方式适合用于管理复杂状态逻辑,如处理多个相关的状态或执行复杂的状态更新操作。
      • 使用 useReducer 配合 useContext ,你可以将状态分发函数共享给整个应用程序,从而实现全局状态管理。这种方式适用于需要在多个组件之间共享状态的场景,例如:登录状态、主题设置等全局状态
    • useState 结合 useContext :

      • useStateReact 提供的基本状态管理方式,它适用于简单状态场景,使用 useState 时,状态更新逻辑通常直接放在组件中,较为简单直观
      • 使用 useContext 配合 useState,你也可以实现全局状态管理,但相比于 useReduceruseState 更适用于简单状态管理需求。这种方式适用于需要共享的状态较少较简单的场景,例如:全局的 UI 状态、一些简单的配置等

5、useCallback:

  • 用于缓存函数的引用,并且在依赖不变的情况下返回相同的函数引用。它的主要特性包括:
    • 用法类似于 useEffect : useCallback的用法类似于 useEffect,都接收一个回调函数和一个依赖数组作为参数。但是,useCallback 返回的是一个记忆化的函数,而 useEffect是用来处理副作用的。
    • 依赖数组: useCallback 接收俩个参数:回调函数依赖数组。当依赖数组中的任何一个依赖发生变化时,useCallback将返回一个新的函数引用,否则返回上一次缓存的函数引用。通过控制依赖数组,可以精确控制函数的重新创建
    • 性能优化:useCallback 可以帮助避免不必要的函数创建重新渲染,从而提升性能,当一个函数作为依赖传递给子组件时,如果函数在每次渲染时重新创建,会导致子组件不必要重新渲染
    • 记忆化函数: useCallback 返回的是一个记忆化版本的函数。当依赖数组中的依赖不发生变化时,useCallback 会返回上一次缓存的函数引用,而不是重新创建新的函数。这可以确保相同的函数引用相同依赖的情况下返回相同的值

6、useMemo:

  • 用于缓存计算结果,并且在依赖不变的情况下返回相同的值。它的主要特性包括:
    • 用法类似于 useEffect: useMemo 的用法类似于 useEffect,都接收一个回调函数和一个依赖数组作为参数。但是,useMemo 是用于缓存计算结果,而 useEffect 是用于处理副作用的。
    • 依赖数组: useMemo 当依赖数组中的任何一个依赖发生变化时,useMemo重新计算值并返回新的值,否则返回上一次缓存的值。通过控制依赖数组,可以精确地控制值重新计算时机
    • 性能优化: useMemo 可以帮助避免不必要计算重新渲染,从而提升性能。当一个计算结果作为依赖传递给子组件时,如果每次渲染重新计算该值,会导致子组件的不必要重新渲染。使用 useMemo 可以确保只在依赖发生变化时才会重新计算值
    • 记忆化值: useMemo 返回的是一个记忆化的值。当依赖数组中的依赖不发生变化时,useMemo 会返回上一次缓存的值,而不是重新计算新的值。这可以确保相同的值相同的依赖情况下返回相同的结果

7、useRef:

  • 用于在函数组件中创建一个可变的 ref 对象。useRef 的主要特性包括:
    • 创建可变的 ref 对象: useRef 可以用来创建一个可变ref 对象,该对象的 current 属性可以在组件的整个生命周期保持不变,并且可以被修改
    • 在函数组件中访问 DOM 元素或其他引用: 通过将 useRef 返回的 ref 对象赋值给 JSX 元素的 ref 属性,可以在函数组件中访问和操作 DOM 元素。此外,useRef 也可以用来存储任何可变的值,而不仅限于 DOM 元素的引用。
    • 不触发重新渲染: 修改 useRef 返回的 ref 对象的 current 属性不会触发组件的重新渲染。因此,useRef 主要用于存储访问组件内部的状态,而不会影响组件的渲染
    • 在多次渲染之间共享数据: useRef 返回的 ref 对象的 current 属性在组件的多次渲染之间保持不变。这意味着可以在不同的渲染之间共享数据,而不需要引入额外状态管理

8、useImperativeHandle:

  • 用于自定义暴露给父组件的实例值(ref)。它的主要特性包括:
    • 在函数组件中模拟类组件的实例属性和方法: 通过 useImperativeHandle,可以在函数组件模拟类组件中的实例属性方法的行为,使得函数组件可以更容易地与父组件进行交互通信
    • 灵活性和性能优化: useImperativeHandle 提供了灵活的方式来控制暴露父组件实例值,可以根据需要选择性地暴露特定属性方法。此外,通过使用 useImperativeHandle 可以避免不必要的属性和方法的暴露,从而提高性能。

9、useLayoutEffect:

  • 与useEffect 类似,但在浏览器执行绘制之前同步触发副作用。

10、useDebugValue:

  • 用于在 React 开发者工具中显示自定义的Hook标签。

11、useErrorBoundary:

  • 用于捕获子组件中的错误并处理它们,通常与 ErrorBounddary组件一起使用。

12、useTransition:

  • 用于在 React Concurrent Mode 中管理并发的 UI 状态转换。

2、react的生命周期

  • React 生命周期方法
    • componentWillMount: (挂载前)在组件被挂载到 DOM 前调用。

    • componentDidMount: (挂载后)在组件被挂载到 DOM 后调用,此时可以进行 DOM 操作。

    • componentWillReceiveProps: (渲染前)在组件接收到新的 props 时调用。

    • shouldComponentUpdate: (渲染后)在组件接收到新的 props 或 state 时调用,返回 true 表示需要更新组件,返回 false 表示不需要更新组件。

    • componentWillUpdate: (更新前)在组件接收到新的 props 或 state 且 shouldComponentUpdate 返回 true 时调用。

    • componentDidUpdate: (更新后)在组件更新后调用,此时可以进行 DOM 操作。

    • componentWillUnmount: (卸载前)在组件将要被卸载时调用。

  • React的useEffect这个hooks模拟生命周期
    • useEffect 函数中的代码在组件挂载后和每次更新后都会执行,模拟了 componentDidMountcomponentDidUpdate 的功能。我们可以在这里执行副作用操作,例如数据获取、DOM 操作等。可以传入一个依赖数组,只有当数组中值发生变化时,才会重新执行 useEffect 中的代码。

    • 如果 useEffect 函数返回一个函数,这个函数将在组件卸载时执行,模拟了 componentWillUnmount 的功能。在这个返回的函数中,我们可以进行清理操作,例如取消订阅清除定时器等。

    • 为了模拟 componentWillUnmount,我们在另一个 useEffect 中传入一个空的依赖数组,使其只在组件挂载时执行一次

      import React, { useState, useEffect } from 'react';
      
      function MyComponent(props) {
        const [someValue, setSomeValue] = useState(props.someValue);
      
        // 相当于 componentDidMount 和 componentDidUpdate
        useEffect(() => {
          // 组件挂载后以及每次更新后都会执行这里的逻辑
          // 更新 someValue 后执行的操作
          console.log('Component did mount or update');
          
          // 如果需要清理副作用,可以在这里返回一个函数
          return () => {
            // 清理副作用的操作
            console.log('Component will unmount');
          };
        }, [someValue]); // 传入依赖项数组来监听特定的值的变化
      
        // 相当于 componentWillUnmount
        useEffect(() => {
          // 只在组件卸载时执行这里的逻辑
          return () => {
            console.log('Component will unmount');
          };
        }, []);
      
        // 4. render
        return (
          // 组件渲染逻辑
        );
      }
      
      export default MyComponent;
      
      

3、为什么需要状态管理

  • React的组件只是通过jsx以及样式,按照state构建最终的UI,真正将页面动态化的实际上是 state 的变化实现的。对于简单的前端应用,在组件中通过组件自身的 state 加上父组件通过props状态传递就能够满足应用数据管理的需求。但是当应用膨胀到一定程度后,就会导致组件内维护的状态非常的复杂,加上组件之间状态的传递,很容易导致数据管理混乱。很小的修改都可能导致难以预料的副作用。

  • 所以我们需要纯净的UI组件,除了渲染逻辑,不再杂糅其他(比如网络请求)。这样我们就要想办法与渲染无关的业务逻辑抽离出来,形成独立的层(在UMI中就是 src/models 文件夹中所管理的 model ) 去管理。让所有组件降级为无状态组件,仅仅依赖 props渲染。这样UI层面就不需关心渲染无关的逻辑,专注做UI渲染。(注:这里说的组件主要是指page下面的页面组件,对于component下的组件本身就应该是比较通用的组件,更应该仅仅依赖props渲染,它们也不应该有model,数据应该通过在页面组件中,去通过props传递过去)。

4、react 状态管理的工具 redux

1、Redux 的核心概念是单一数据源状态不可变,它通过统一的数据流来管理应用的状态

  • 单一数据源: Redux 应用的状态被存储在一个单一的 JavaScript 对象中,称为状态树(State Tree)或状态存储(Store)。这个状态对象代表了整个应用的状态,使得应用的状态变化变得可预测容易管理

  • 状态不可变Redux 要求状态是不可变的,即状态对象不能直接修改,而是通过纯函数来产生新的状态。这种不可变性保证了状态的可预测性可控性,避免了副作用的产生。

  • 纯函数: 在 Redux 中,所有的状态变化都由纯函数(又称为 reducer)来处理。Reducer 接收当前状态和一个描述动作的对象作为参数,并返回一个新的状态。Reducer 函数必须是纯函数,即给定相同的输入,始终返回相同的输出,且不产生副作用。

  • 单向数据流Redux 遵循单向数据流的设计模式,数据的流动是单向的,即由视图(View)发起动作(Action),通过 Reducer 更新状态(State),再重新渲染视图。这是一种数据流的设计使得状态变化的过程清晰可控

  • 动作(Action): 动作是一个描述状态变化纯 JavaScript 对象,它必须包含一个 type 字段来指示动作类型,可以包含任意其他字段来描述动作所需的数据

  • 分发器(Dispatcher)Redux 使用分发器派发动作,将动作发送给 Reducer 进行状态更新分发器Redux 的核心机制之一,通过调用 dispatch(action) 方法来分发动作。

  • 订阅器(Subscriber)Redux 提供了订阅器机制,允许组件订阅状态的变化,当状态发生变化时,订阅器会通知所有的订阅者,并重新渲染相关的组件。

2. 核心概念

  • Store
    • Redux 应用中唯一的状态容器。它包含了应用的整个状态树(state tree)。应用中只能有一个 store。
    import { createStore } from 'redux';
    const store = createStore(reducer);
    
  • Action
    • 动作是一个描述发生了什么的普通 JavaScript 对象。每个动作必须有一个 type 属性,表示动作的类型。动作可以携带其他数据。
    const incrementAction = { type: 'INCREMENT' };
    const decrementAction = { type: 'DECREMENT' };
    
  • Reducer
    • Reducer 是一个纯函数,接收当前的状态和一个动作,并返回一个新的状态。Reducer 根据动作的类型决定如何更新状态。
    const initialState = { count: 0 };
    
    function counterReducer(state = initialState, action) {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 };
        case 'DECREMENT':
          return { count: state.count - 1 };
        default:
          return state;
      }
    }
    
  • Action Creator
    • Action Creator 是一个函数,用于创建动作。它返回一个动作对象。
    function increment() {
      return { type: 'INCREMENT' };
    }
    
    function decrement() {
      return { type: 'DECREMENT' };
    } 
    
  • Dispatch
    • dispatch 是 store 提供的一个方法,用于发送动作到 reducer。
    store.dispatch(increment());
    store.dispatch(decrement()); 
    
  • Selector
    • Selector 是一个函数,用于从 store 中提取部分状态。它通常用于在组件中访问状态。
    const selectCount = (state) => state.count;
    

3. Redux 中的数据流

  • Redux 的数据流是单向的,遵循以下步骤:
    • 调用 store.dispatch(action) 发送一个动作。
    • Redux store 调用根 reducer 函数,并传入当前状态树和动作。
    • 根 reducer 通过拆分 reducer 树来计算新的状态树。
    • Redux store 保存新的状态树,并触发订阅的监听器(如 React 组件的 mapStateToProps)。

4. 中间件

  • 中间件提供了在 dispatch 动作和到达 reducer 之间的扩展点。它常用于处理异步操作、日志记录和错误报告等。
  • 常见的中间件有 redux-thunk 和 redux-saga:
    • redux-thunk 允许你写返回函数的 action creator,这个函数可以包含异步逻辑。
    import thunk from 'redux-thunk';
    const store = createStore(rootReducer, applyMiddleware(thunk));
    
    function fetchData() {
      return (dispatch) => {
        fetch('/api/data')
          .then(response => response.json())
          .then(data => dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data }))
          .catch(error => dispatch({ type: 'FETCH_DATA_ERROR', error }));
      };
    } 
    
    • redux-saga 使用生成器函数处理异步操作,允许更复杂的控制流。
    import createSagaMiddleware from 'redux-saga';
    import { call, put, takeEvery } from 'redux-saga/effects';
    
    function* fetchDataSaga() {
      try {
        const data = yield call(fetch, '/api/data');
        yield put({ type: 'FETCH_DATA_SUCCESS', payload: data });
      } catch (error) {
        yield put({ type: 'FETCH_DATA_ERROR', error });
      }
    }
    
    function* watchFetchData() {
      yield takeEvery('FETCH_DATA_REQUEST', fetchDataSaga);
    }
    
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
    sagaMiddleware.run(watchFetchData);
    

5. 使用 Redux 与 React 集成

  • Redux 通常与 React 一起使用,使用 react-redux 提供的 Provider 和 connect。
  • Provider 使 Redux store 可用于应用中的所有组件。
import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}>
      <MyComponent />
    </Provider>
  );
} 
  • connect 将组件连接到 Redux store,允许组件访问状态和 dispatch actions。
import { connect } from 'react-redux';
import { increment, decrement } from './actions';

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

const mapStateToProps = (state) => ({ count: state.count });
const mapDispatchToProps = { increment, decrement };

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

6. Redux Toolkit

  • Redux Toolkit 是 Redux 官方推荐的工具包,简化了 Redux 的使用。它包含了 configureStore、createSlice 和 createAsyncThunk 等API,简化了 store 配置、reducers 和异步操作的定义。
  • createSlice 自动生成 actions 和 reducers。
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: (state) => state + 1,
    decrement: (state) => state - 1,
  },
});

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
});

export const { increment, decrement } = counterSlice.actions;
export default store;
  • createAsyncThunk 处理异步逻辑。
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

export const fetchUserById = createAsyncThunk(
  'users/fetchById',
  async (userId, thunkAPI) => {
    const response = await fetch(`/api/user/${userId}`);
    return response.json();
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: { entities: {}, loading: 'idle' },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserById.pending, (state) => {
        state.loading = 'loading';
      })
      .addCase(fetchUserById.fulfilled, (state, action) => {
        state.entities[action.payload.id] = action.payload;
        state.loading = 'idle';
      })
      .addCase(fetchUserById.rejected, (state) => {
        state.loading = 'failed';
      });
  },
});

export default userSlice.reducer;

7.redux-persist的工作原理

  • redux-persist是一个用于在redux应用中持久化状态的库,它的工作原理主要基于将redux-store状态保存到持久存储(如本地存储或者AsyncStorage)中去,并在应用重新加载时存储中还原状态回去
// store.js
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // 使用 localStorage 作为默认存储
import rootReducer from './reducers'; // 假设有一个根 reducer

const persistConfig = {
    key: 'root',
    storage,
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
    serializableCheck: {
        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
    },
    }),
});

const persistor = persistStore(store);

export { store, persistor };

  • redux使用:
// 创建 Redux Store 时,将 Redux Thunk 中间件应用到 Store 中。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(rootReducer, applyMiddleware(thunk));

export default store;

// 使用 Redux Thunk,你可以创建返回函数的 Action Creator,而不仅仅是返回一个 action 对象。这个函数可以接收 dispatch 和 getState 参数,允许你在函数内部执行异步操作。
import axios from 'axios';

export const fetchData = () => {
    return async (dispatch, getState) => {
        dispatch({ type: 'FETCH_DATA_REQUEST' });

        try {
        const response = await axios.get('https://api.example.com/data');
        dispatch({ type: 'FETCH_DATA_SUCCESS', payload: response.data });
        } catch (error) {
        dispatch({ type: 'FETCH_DATA_FAILURE', error: error.message });
        }
    };
};

// 在组件中使用 dispatch 调用异步 Action。
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchData } from './actions';

function MyComponent() {
    const dispatch = useDispatch();
    const data = useSelector(state => state.data);

    useEffect(() => {
        dispatch(fetchData());
    }, [dispatch]);

    return (
        <div>
        {/* 渲染数据 */}
        </div>
    );
}

export default MyComponent;

5、什么是高阶组件

高阶组件(Higher-Order Component,简称 HOC)是 React 中的一种设计模式,用于复用组件逻辑。高阶组件本质上是一个函数,它接受一个组件并返回一个新的组件。这个新的组件会增强修改原始组件的功能

1、主要特点:

  • 不改变原有组件:

    • HOC 通过将原有组件作为子组件来增强功能,而不会修改原有组件的实现。
  • 复用逻辑:

    • 可以在多个组件中复用相同的逻辑,比如数据获取、权限控制、日志记录等。
  • 提升代码的可维护性:

    • 通过分离关注点,将逻辑从组件中抽离出来,使得代码更加清晰和易于维护。

2、高阶组件的使用场景:

  • 权限控制:

    • 通过 HOC 来控制组件的访问权限。
  • 日志记录:

    • 记录用户的操作或组件的生命周期事件。
  • 数据获取:

    • 将数据获取逻辑封装在 HOC 中,使得数据获取逻辑可以复用于多个组件。

3、注意事项:

  • 不要在 HOC 中改变原始组件的行为:

    • HOC 应该增强组件而不是修改其原有行为。
  • 传递所有的 props:

    • HOC 应该将所有接收到的 props 传递给被包裹的组件,以确保组件的灵活性和可复用性。
  • 避免在 render 方法中使用 HOC:

    • 每次 render 时创建新的 HOC 会导致性能问题,应尽量在组件外部定义和使用 HOC。

4、高阶组件是一个函数,定义如下:

// 接收一个组件参数
const withDataFetching = (url) => (WrappedComponent) => {
  return function EnhancedComponent(props) {
    const [data, setData] = useState(null);

    useEffect(() => {
      fetch(url)
        .then((response) => response.json())
        .then((data) => setData(data))
        .catch((error) => console.error(error));
    }, [url]);

    return <WrappedComponent data={data} {...props} />;
  };
}; 

6、 react 应用程序执行步骤流程:

一个 React 应用程序的执行步骤可以分为以下几个阶段:

1、初始化阶段(Initialization):

  • 当浏览器加载 React 应用时,React 库会初始化运行环境。
  • React 根据入口组件的定义,开始创建虚拟 DOM 树
  • React 创建根组件的组件实例,并将其挂载到虚拟 DOM 树中。
  • 初始化阶段包括了初始化组件创建 Fiber 树等操作。

2、挂载阶段:

  • React 根据组件层次结构,递归地创建虚拟 DOM 树,并将根组件虚拟 DOM 节点根容器(例如 div#root)关联
  • 如果是函数组件,则直接调用函数本身。
    • 对于函数组件,React会在Hooks执行过程中调用相应的生命周期替代方法,比如useEffect
    • 如果组件中包含副作用操作,比如数据获取订阅事件等,这些副作用操作会在组件挂载阶段被执行
    • 如果组件包含子组件,则会递归执行子组件挂载过程,直至整个组件树都被挂载完成
  • 如果是类组件,则会调用构造函数(constructor)来初始化组件状态(state)和属性(props)。
    • 对于类组件,会依次执行生命周期方法,包括componentWillMount(即将废弃)、 UNSAFE_componentWillMountcomponentDidMount等(如果有)。

3、调度阶段(Scheduling):

* 调度阶段通过 `Fiber 架构`实现`异步渲染``优先级控制`,使得 React 能够更灵活地`处理更新`任务。
* `调度器`使用 `Fiber` `数据结构`来实现对`更新任务``调度``管理`* 调度器根据`更新任务``优先级`,决定`何时执行`更新任务。
* React 使用`调和算法`比较新`旧虚拟 DOM 树的差异`,确定`需要更新的部分`* 调度阶段还包括了更新任务的`拆分``排队`等操作。

4、渲染阶段(Render):

  • React 开始执行更新任务,进行实际的 DOM 更新操作
  • 在渲染阶段,React 根据更新任务生成Fiber 树,进行递归遍历,更新实际的 DOM 节点。
  • React 根据 Fiber 节点的类型状态,执行相应的更新操作,包括创建、更新删除 DOM 节点等。
  • 渲染阶段还包括了执行组件生命周期方法、函数组件的执行等操作。
  • 在渲染组件时,React 执行组件的 render 方法,并生成组件的虚拟 DOM 子树
  • React 根据 Fiber 节点的类型和状态,执行相应的更新操作,更新组件状态和属性,并生成新的虚拟 DOM 子树。

5、更新阶段(Update):

  • 当应用状态发生变化,或者接收到外部事件(比如用户输入)时,触发更新。 React 使用调和(Reconciliation)算法,比较新旧虚拟 DOM 树的差异,确定需要更新的部分。
  • 更新阶段包括了调和算法的执行、生成更新任务列表等操作。
  • 在更新阶段,React 根据渲染阶段生成的 Fiber 树,执行实际的更新任务
  • React 根据 Fiber 节点的类型状态,执行相应的更新操作,更新组件状态属性,并生成新虚拟 DOM 子树
  • 更新阶段可能包括调用组件生命周期方法(如 componentDidUpdate)、执行副作用操作(如 useEffect)等。

6、提交阶段(Commit):

  • 渲染阶段更新阶段完成后,React 进入提交阶段,将更新结果同步实际DOM 上。
  • React 通过调用渲染器(Renderer)提供的接口,将更新内容提交到浏览器中的 DOM
  • 提交阶段包括了执行 DOM 更新操作触发生命周期方法、更新完成回调等操作。

7、交互阶段:

  • 用户应用程序进行交互时,例如点击按钮、输入文本等,会触发相应的事件。
  • React 的事件系统捕获处理这些事件,并更新组件的状态属性
  • 更新后的组件状态触发重新渲染,React 根据更新策略重新执行渲染更新流程。

8、卸载阶段:

  • 当组件不再需要时,React 执行组件的卸载操作,从虚拟 DOM 树实际 DOM 中移除组件
  • 在卸载过程中,React 执行组件的生命周期方法(如 componentWillUnmount),释放资源取消订阅等操作。

在整个执行过程中,React 会根据 Fiber 架构的设计,实现对更新过程可中断可暂停可恢复,使得 React 应用在性能用户体验上表现更加优秀。这些阶段相互配合,确保了 React 应用的正确渲染响应性

7、什么是 React Fiber?它解决了什么问题?

1、Fiber 是 React 库中的一种重新实现核心算法。它是 React v16 中的一项重大改进,旨在提高 React 应用程序的性能交互性。主要目标是实现增量式渲染,即能够在浏览器空闲时分片处理中断渲染任务,从而更好地控制任务优先级,提高应用的响应速度流畅度

  • 更好的任务调度和优先级控制: Fiber 架构允许 React 在不同任务之间进行切换,使得可以更灵活地处理不同优先级的任务,比如响应用户输入更新 UI。这种调度方式有助于避免长时间的 UI 阻塞,提高了应用的流畅度

  • 增量式渲染: React Fiber 实现了一种增量式渲染的方式,可以将渲染任务分成小块,通过优先级调度,在不同帧之间完成渲染,从而避免了长时间渲染阻塞,保证了更好的用户体验。

  • 更好的错误处理和调试: Fiber 架构为 React 提供了更好的错误处理机制,可以更好地捕获报告错误,提供了更好的调试工具和方式,帮助开发者更快地定位解决问题

8、React Fiber 如何处理优先级?

  • React Fiber 处理优先级的方式基于任务调度算法。它通过将任务分成多个优先级,并使用调度器来确定何时执行哪个任务,从而实现优先级控制
  • 具体来说,React Fiber 使用了一个叫做 "Scheduler" 的模块来处理任务的调度优先级。Scheduler 负责决定何时执行任务,并且根据任务的优先级来调度执行顺序。
  • React Fiber 中的优先级分为多个级别,通常包括以下几个:
    • 同步(Sync)最高优先级的任务,立即执行。通常用于处理用户交互事件或者紧急更新

    • 批处理(Batched): 用于处理批量更新,例如在 setState调用的更新。这些更新会被推迟当前帧末尾执行

    • 动画(Animation): 用于处理与动画相关的更新,例如通过 requestAnimationFrame 调度的任务。这些更新的优先级较高,以确保动画的流畅性

    • 优先(High): 用于处理重要但非紧急的更新,例如路由转换某些数据加载。这些更新会在动画更新之后但在一般批处理更新之前执行。

    • 正常(Normal)大多数更新属于这个优先级,包括一般的 UI 更新数据加载

    • 低(Low)最低优先级的更新,例如预加载其他后台任务。这些更新会在其他更新完成后执行,并且可能会被中断以让更高优先级的任务执行。

9、请解释 React Fiber 中的协调过程。

  • React Fiber 中的协调过程是指 React 如何处理组件树中的变化,以及如何通过协调算法确定何时以及如何更新组件树。
  • 在 React 中,当组件的状态或属性发生变化时,React 需要决定如何更新 UI 以反映这些变化。React Fiber 使用了一种称为 "协调" 的过程来处理这些变化。以下是 React Fiber 中的协调过程:
    • 触发更新: 当组件的状态或属性发生变化时,React 会调度更新,将变化传播到组件树中的相关节点。

    • 构建更新队列: React 会构建一个更新队列,记录需要更新的组件以及它们的变化。

    • 协调阶段(Reconciliation Phase): 在协调阶段,React Fiber 会对更新队列进行遍历,比较新的虚拟 DOM 树与之前的虚拟 DOM 树,以确定需要进行哪些更新。在此过程中,React 使用了一种称为 "diffing" 的算法来比较两棵树的差异,并确定需要更新的部分。

    • 生成 Fiber 树: 在协调阶段,React Fiber 会创建一棵称为 Fiber 树的数据结构,用于表示组件树的结构以及更新的状态。Fiber 树包含了每个组件的 Fiber 节点,并且记录了每个节点的状态、类型以及与其他节点的关系。

    • 更新阶段(Commit Phase): 在更新阶段,React Fiber 会基于协调阶段的结果,对实际的 DOM 进行更新操作。React 使用一种称为 "提交" 的过程来将更新应用到 DOM 上,确保更新的一致性和性能。

10、什么是任务调度器(Scheduler)在 React Fiber 中的作用?

  • 在 React Fiber 中,任务调度器(Scheduler)是负责协调任务执行顺序的模块。它的作用主要是决定何时执行哪个任务,并根据任务的优先级来调度任务的执行顺序,以保证系统的响应性和性能。
  • 任务调度器在 React Fiber 中扮演着至关重要的角色,它的主要作用包括:
    • 任务调度: 任务调度器决定了哪些任务应该在何时执行。例如,当用户触发了一个事件或者发生了状态更新时,任务调度器会决定相应的更新任务何时被调度执行。

    • 优先级控制: 任务调度器根据任务的优先级来确定任务的执行顺序。不同的任务可能具有不同的优先级,例如用户交互事件可能具有更高的优先级,而后台数据加载可能具有较低的优先级。任务调度器根据这些优先级来决定任务的执行顺序,以确保高优先级任务能够及时得到处理。

    • 中断和恢复: 任务调度器能够中断正在执行的任务,并在适当的时机恢复执行。这种能力使得 React Fiber 能够实现增量式渲染,即在浏览器空闲时分片处理和中断渲染任务,从而更好地控制任务优先级,提高应用的响应速度和流畅度。

    • 协调任务执行: 任务调度器与协调器(Reconciler)密切合作,确保任务的执行顺序与协调器确定的更新顺序一致。任务调度器会根据协调器的输出,将更新任务按照正确的顺序调度执行,以确保更新的一致性和正确性。

11、请解释 React Fiber 中的时间分片(Time Slicing)。

  • React Fiber 中的时间分片(Time Slicing)是一种增量式渲染的技术,旨在将渲染任务分解成多个小的时间片段,在多个帧之间分散执行,以避免长时间的渲染阻塞,提高用户界面的响应性和流畅性。

  • 具体来说,时间分片技术使得 React 能够在浏览器的空闲时间内执行渲染任务,而不是一次性地将所有任务集中在一个帧中完成。这样一来,React 可以将大型渲染任务分解成多个小的时间片段,并在每个时间片段中执行一部分任务。如果在某个时间片段结束时,浏览器仍然处于空闲状态,React 就可以继续执行下一个时间片段的任务,直到完成整个渲染任务。

  • 时间分片的核心思想是将长时间的任务分解成多个短时间的任务,并利用浏览器的空闲时间逐步完成这些任务,从而避免了长时间的 UI 阻塞,保证了用户界面的响应性和流畅性。

  • React Fiber 使用时间分片技术的主要目的是:

    • 减少渲染阻塞: 将长时间的渲染任务分解成多个小的时间片段,在多个帧之间分散执行,可以避免长时间的渲染阻塞,提高了用户界面的响应速度。

    • 提高用户体验: 通过保持界面的响应性和流畅性,用户可以更快地与应用程序进行交互,获得更好的用户体验。

    • 优化任务调度: 时间分片技术使得 React 能够更好地控制任务的执行顺序,根据优先级和浏览器的空闲状态动态调度任务,从而提高了任务调度的效率和性能。

12、如何中止或暂停 Fiber 树的渲染?

  • 在 React 中,目前没有直接的 API 可以中止或暂停 Fiber 树的渲染。React 的设计初衷是为了保持应用程序的响应性,并尽可能快速地响应用户的操作和更新。

  • 然而,有一些间接的方法可以实现类似的效果:

    • 条件渲染: 可以通过条件渲染来控制组件的渲染。根据特定的状态或条件,可以选择性地渲染组件或者返回 null,从而达到暂停渲染的效果。

    • 组件状态管理: 可以通过管理组件的状态来控制组件的渲染。通过控制组件状态的更新时机,可以在需要时暂停或恢复渲染。

    • 性能优化: 通过使用 React 提供的性能优化技术,可以减少不必要的渲染和重新渲染,从而达到减少渲染次数的效果。例如,使用 PureComponent 或 React.memo 进行组件的浅比较,以减少不必要的渲染。

    • 动态加载组件: 可以使用懒加载技术来延迟加载组件,从而减少初始渲染的时间和资源消耗。通过动态加载组件,可以在需要时才开始渲染组件,从而减少不必要的渲染。

13、React Fiber 中的工作单元(Work Unit)是什么?它们如何协同工作?

  • 在 React Fiber 中,工作单元(Work Unit)是指一个可执行的任务单元,通常对应于组件的更新或其他一些操作。每个工作单元都包含了执行所需的信息,例如要执行的函数、组件的类型、属性等。

  • 工作单元在 React Fiber 中的协同工作可以概括为以下几个步骤:

    • 构建工作单元: 当触发更新时,React 将会创建一个或多个工作单元,用于表示需要执行的任务。这些工作单元可能包括组件的更新、状态更新、属性变更等。

    • 调度工作单元: 调度器(Scheduler)负责决定何时执行这些工作单元,并根据优先级调度它们的执行顺序。React 使用一种称为 "任务调度器" 的算法来管理工作单元的执行。

    • 执行工作单元: 一旦工作单元被调度执行,React Fiber 将开始执行工作单元中的任务。这可能包括执行组件的生命周期方法、更新组件的状态、计算 Virtual DOM 的变更等。

    • 更新 Fiber 树: 在执行工作单元期间,React Fiber 会根据工作单元的执行结果更新 Fiber 树。这可能涉及到创建新的 Fiber 节点、更新现有的 Fiber 节点的状态等操作。

    • 提交更新: 一旦所有工作单元都被执行并且 Fiber 树已经更新,React Fiber 将会提交更新到实际的 DOM 上。这个过程通常发生在渲染阶段的最后阶段。

14、在 React Fiber 中,如何处理异常?

  • 在 React Fiber 中,异常处理是通过捕获和处理 JavaScript 错误来实现的。React Fiber 提供了一种机制来捕获组件生命周期方法、事件处理函数和渲染函数中发生的错误,并且提供了一些选项来处理这些错误。

  • 具体来说,React Fiber 中的异常处理可以分为以下几个方面:

    • 错误边界(Error Boundary): 错误边界是一种特殊的 React 组件,它能够捕获其子组件树中发生的 JavaScript 错误,并且在发生错误时渲染备用 UI。通过将错误边界组件包裹在应用程序的特定部分,可以限制错误的影响范围,并且提供更友好的错误提示给用户。

    • 组件生命周期方法: 在 React Fiber 中,如果组件的生命周期方法(如 componentDidMount、componentDidUpdate、componentWillUnmount 等)中发生了错误,React Fiber 会捕获这些错误,并且通过错误边界或者其他方式来处理这些错误。

    • 事件处理函数: 如果事件处理函数中发生了错误,React Fiber 会捕获这些错误,并且在控制台中输出错误信息。开发者可以选择使用 try...catch 语句来显式捕获这些错误,并且根据具体情况进行处理。

    • 渲染函数: 如果渲染函数中发生了错误,React Fiber 会捕获这些错误,并且通过错误边界或者其他方式来处理这些错误。开发者可以选择使用 try...catch 语句来显式捕获这些错误,并且根据具体情况进行处理。

18、next.js有几种预渲染?

  • 静态生成 (SSG)

    • 在构建时 (build time) 生成页面的 HTML,并在每个请求时提供该预生成的 HTML。

    • 适用于内容不频繁变化的页面,如博客、产品页面等。

    • 使用 getStaticProps 函数来获取数据,并在构建时生成页面。

    • 可以通过 getStaticPaths 在构建时动态生成路径。

  • 服务器端渲染 (SSR):

    • 在每个请求时动态生成页面的 HTML。

    • 适用于内容频繁变化的页面,如社交媒体新闻流等。

    • 使用 getServerSideProps 函数在每个请求时获取数据并生成页面。

16、next.js的预渲染原理

  • 在 Next.js 中,打包时会检测页面组件文件中是否存在 getServerSidePropsgetStaticProps 这两个特定的函数。这两个函数用于获取页面所需的数据,并在预渲染时提供数据生成静态 HTML。Next.js 根据这些函数的存在与否来确定页面是否需要预处理,从而进行相应的预渲染处理。
    • 如果页面组件文件中包含getServerSideProps 函数,则 Next.js 将在每个请求动态生成页面的 HTML,这就是服务器端渲染(SSR)。
    • 如果页面组件文件中包含getStaticProps 函数,则 Next.js 将在构建时生成页面的静态 HTML,并在每个请求时提供预生成的 HTML,这就是静态生成(Static Generation)。

17、Next 在 yarn build 执行了哪些?

  • 如果你的项目配置TypeScript,Next.js 会先进行类型检查

  • 同时,Next.js 还会进行代码风格检查(Linting),以确保代码符合指定的规范

  • Next.js 使用 Babel 编译你的 JavaScriptTypeScript 代码。Babel 会将现代 JavaScript 语法转换为更广泛兼容的版本,并处理 JSX 语法。

  • Next.js 使用 Webpack 对你的代码进行打包。Webpack解析你的应用程序依赖关系,并生成优化后的静态文件。

  • 对于使用静态生成 (Static Generation) 的页面,Next.js 会在构建时调用 getStaticPropsgetStaticPaths,并生成对应的 HTML 文件。

  • 这些静态页面将被预渲染,并存储HTML 文件,以便在请求快速提供给客户端。

  • 对于使用服务器端渲染 (Server-Side Rendering) 的页面,Next.js 不会在构建时生成 HTML 文件,而是会生成用于在服务器上渲染这些页面的代码。

  • 每次请求时,Next.js 会调用 getServerSideProps,生成页面的 HTML,并发送给客户端。

  • 对于使用增量静态再生 (Incremental Static Regeneration) 的页面,Next.js 会在构建时生成初始的静态页面,并在后台定期更新这些页面。

  • Next.js 会设置再生成时间间隔,并在该间隔之后重新生成页面。

  • Next.js 会生成一个 .next 目录,其中包含了所有构建结果,包括打包后的静态文件预渲染的 HTML 文件服务器端渲染的代码等。

  • Next.js 会对所有静态资源(如 CSSJavaScript图片等)进行优化。包括文件压缩代码分割静态资源哈希化处理等。

  • 这些优化步骤可以减少文件大小加快加载速度,并提高应用的性能

18、react的useState如何触发update函数而不触发渲染,为什么?

  • 可以定义一个引用类型,如:数组、对象。通过修改state后再set回update函数中。
const [items, setItems] = useState([1, 2]);

// 不会触发 render
const notUpdate = () => {
  // 直接修改数组
  items.push(items.length + 1);
  setItems(items);  // 使用相同的数组引用
};

// 会触发 render
const update = () => {
  setItems(prevItems => [...prevItems, prevItems.length + 1]);
};
  • React 的状态更新机制是基于浅比较的,因此只有当状态引用发生变化时,才会触发组件的重新渲染。如果你修改了引用类型的值(如对象或数组)但引用本身没有变化,React 不会检测到状态的变化,从而不会重新渲染组件。

19、React中如何处理表单输入和管理表单状态?

  • 可以使用受控组件和非受控组件来处理表单输入。受控组件的状态由React控制,通过设置value属性和onChange事件来管理状态。例如:
function MyForm() {
    const [value, setValue] = useState('');
    
    const handleChange = (e) => {
        setValue(e.target.value);
    };
    
    return (
        <input type="text" value={value} onChange={handleChange} />
    );
}

20、在项目中使用过哪些React性能优化技巧?

  • 使用React.memo来避免不必要的重新渲染。

  • 使用useCallback和useMemo来缓存函数和计算结果。

  • 避免在render方法中创建新的函数和对象。

  • 使用React.lazy和Suspense进行代码分割和按需加载。

  • 避免大数据列表的全量渲染,使用虚拟滚动技术(如react-window)。

21、如何在React中实现代码分割?

  • 可以使用React的React.lazy和Suspense来实现代码分割。
const OtherComponent = React.lazy(() => import('./OtherComponent'));

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

22、如何优化 Redux 应用的性能?

  • 拆分 reducer: 将状态拆分成多个小的 reducer,使用 combineReducers 合并。

  • 避免不必要的 re-render: 使用 react-redux 的 connect 和 useSelector 时,确保只选择必要的状态,避免不必要的重新渲染。

  • 使用 memoization: 使用 reselect 库创建 memoized selector,避免重复计算。

  • 异步加载数据: 仅在需要时加载数据,避免在组件挂载时进行大量的异步操作。

23、使用过 Redux Toolkit 吗?它的优势是什么?

  • 简化配置: 提供了 configureStore 和 createSlice 等简化配置的方法。

  • 内置工具: 集成了 Redux Thunk、中间件和开发工具扩展。

  • 减少样板代码: 自动生成 action 和 reducer,减少手动编写的样板代码。

24、什么是 action 和 action creator?

  • Action: 一个包含 type 属性的普通对象,type 描述了发生的事件,可能还包括一些数据(payload)。
const action = {
  type: 'INCREMENT',
  payload: 1
};
  • Action Creator: 返回 action 对象的函数,用于封装 action 的创建。
const increment = (value) => ({
  type: 'INCREMENT',
  payload: value
});

25、什么是 prop drilling,如何避免?

* Prop drilling 是指在 React 应用中,将 props 通过多层组件层层传递的过程。当组件树的层级很深时,可能会导致 props 需要在多个中间组件中传递,这样会增加组件之间耦合性,使得代码维护困难

  • 使用 Context API: Context API 允许在组件树中共享数据,避免了 props 需要逐层传递的问题。通过创建上下文(context),可以在任何层级的组件中访问共享的数据,而不需要手动传递 props。

  • 使用状态管理库: 像 Redux、MobX 等状态管理库可以帮助管理全局状态,避免了 props 在组件树中的传递。通过将状态存储在全局的状态容器中,任何组件都可以访问和更新这些状态,而不需要通过 props 传递。