react的知识点
1、react的所有hooks:
1、useState:
-
用于在函数组件中添加状态。它返回一个由
当前状态和更新函数组成的数组。 -
useState 以
数组形式返回是为了方便使用ES6的解构赋值语法来获取当前状态值和更新状态的函数。 -
useState的执行是同步的,而状态更新是异步的。状态更新会触发渲染函数。
-
当调用
useState返回的更新函数时,react会将更新加入队列中,然后再适当的时机(通常在下一个渲染周期)执行队列中的更新操作。这个过程会触发组件的重新渲染,从而更新组件中的UI以反映最新的状态。 -
每当
状态更新时,React会重新运行组件函数,并根据新的状态值生成新的虚拟 DOM,然后与旧的虚拟 DOM进行比较,并只更新发生变化的部分。这样就实现了局部更新,而不是整个页面重新渲染。
2、useEffect:
-
用于在组件渲染完成后执行副作用操作,如订阅、网络请求、DOM操作等。
-
useEffect 能模拟类组件的三个生命周期:
componentDidMount、componentDidUpdate和componentWillUnmount -
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提供了一种更复杂的状态管理方式。它类似于Redux的reducer,接受一个状态和一个操作,并返回新的状态。 -
基本用法:
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进行全局状态管理:- 可以将
useReducer与useContext结合使用,实现全局状态管理。首先创建一个上下文提供者,然后使用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:useState是React提供的基本的状态管理方式,它适用于简单的状态场景,使用useState时,状态更新逻辑通常直接放在组件中,较为简单直观。- 使用
useContext配合useState,你也可以实现全局状态管理,但相比于useReducer,useState更适用于简单的状态管理需求。这种方式适用于需要共享的状态较少或较简单的场景,例如:全局的 UI 状态、一些简单的配置等。
-
5、useCallback:
- 用于缓存函数的引用,并且在依赖不变的情况下返回相同的函数引用。它的主要特性包括:
- 用法类似于 useEffect :
useCallback的用法类似于useEffect,都接收一个回调函数和一个依赖数组作为参数。但是,useCallback返回的是一个记忆化的函数,而useEffect是用来处理副作用的。 - 依赖数组:
useCallback接收俩个参数:回调函数、依赖数组。当依赖数组中的任何一个依赖发生变化时,useCallback将返回一个新的函数引用,否则返回上一次缓存的函数引用。通过控制依赖数组,可以精确的控制函数的重新创建。 - 性能优化:
useCallback可以帮助避免不必要的函数创建和重新渲染,从而提升性能,当一个函数作为依赖传递给子组件时,如果函数在每次渲染时都重新创建,会导致子组件不必要的重新渲染。 - 记忆化函数:
useCallback返回的是一个记忆化版本的函数。当依赖数组中的依赖不发生变化时,useCallback会返回上一次缓存的函数引用,而不是重新创建新的函数。这可以确保相同的函数引用在相同依赖的情况下返回相同的值。
- 用法类似于 useEffect :
6、useMemo:
- 用于缓存计算结果,并且在依赖不变的情况下返回相同的值。它的主要特性包括:
- 用法类似于 useEffect:
useMemo的用法类似于useEffect,都接收一个回调函数和一个依赖数组作为参数。但是,useMemo是用于缓存计算结果的值,而useEffect是用于处理副作用的。 - 依赖数组:
useMemo当依赖数组中的任何一个依赖发生变化时,useMemo将重新计算值并返回新的值,否则返回上一次缓存的值。通过控制依赖数组,可以精确地控制值的重新计算时机。 - 性能优化:
useMemo可以帮助避免不必要的计算和重新渲染,从而提升性能。当一个计算结果作为依赖传递给子组件时,如果每次渲染都重新计算该值,会导致子组件的不必要重新渲染。使用useMemo可以确保只在依赖发生变化时才会重新计算值。 - 记忆化值:
useMemo返回的是一个记忆化的值。当依赖数组中的依赖不发生变化时,useMemo会返回上一次缓存的值,而不是重新计算新的值。这可以确保相同的值在相同的依赖情况下返回相同的结果。
- 用法类似于 useEffect:
7、useRef:
- 用于在函数组件中创建一个可变的 ref 对象。useRef 的主要特性包括:
- 创建可变的 ref 对象:
useRef可以用来创建一个可变的ref 对象,该对象的current属性可以在组件的整个生命周期中保持不变,并且可以被修改。 - 在函数组件中访问 DOM 元素或其他引用: 通过将
useRef返回的ref对象赋值给JSX元素的ref属性,可以在函数组件中访问和操作DOM元素。此外,useRef也可以用来存储任何可变的值,而不仅限于DOM元素的引用。 - 不触发重新渲染: 修改
useRef返回的ref对象的current属性不会触发组件的重新渲染。因此,useRef主要用于存储和访问组件内部的状态,而不会影响组件的渲染。 - 在多次渲染之间共享数据:
useRef返回的ref对象的current属性在组件的多次渲染之间保持不变。这意味着可以在不同的渲染之间共享数据,而不需要引入额外的状态管理。
- 创建可变的 ref 对象:
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 函数中的代码在组件
挂载后和每次更新后都会执行,模拟了componentDidMount和componentDidUpdate的功能。我们可以在这里执行副作用操作,例如数据获取、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。 - 如果
组件中包含了副作用操作,比如数据获取、订阅事件等,这些副作用操作会在组件挂载阶段被执行。 - 如果组件
包含子组件,则会递归执行子组件的挂载过程,直至整个组件树都被挂载完成。
- 对于函数组件,React会在
- 如果是
类组件,则会调用构造函数(constructor)来初始化组件的状态(state)和属性(props)。- 对于
类组件,会依次执行生命周期方法,包括componentWillMount(即将废弃)、UNSAFE_componentWillMount、componentDidMount等(如果有)。
- 对于
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 中,
打包时会检测页面组件文件中是否存在getServerSideProps或getStaticProps这两个特定的函数。这两个函数用于获取页面所需的数据,并在预渲染时提供数据或生成静态 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编译你的JavaScript和TypeScript代码。Babel会将现代JavaScript语法转换为更广泛兼容的版本,并处理JSX语法。 -
Next.js 使用
Webpack对你的代码进行打包。Webpack会解析你的应用程序的依赖关系,并生成优化后的静态文件。 -
对于使用
静态生成 (Static Generation)的页面,Next.js 会在构建时调用getStaticProps和getStaticPaths,并生成对应的 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 会对所有
静态资源(如CSS、JavaScript、图片等)进行优化。包括文件压缩、代码分割、静态资源的哈希化处理等。 -
这些优化步骤可以
减少文件大小,加快加载速度,并提高应用的性能。
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 传递。