前端常见面试题之react高级特性(Portals、Context、HOC等)

文章目录

  • [1. Portals将子组件渲染到父组件以外的DOM节点上](#1. Portals将子组件渲染到父组件以外的DOM节点上)
  • [2. Context组件树中传递数据](#2. Context组件树中传递数据)
  • [3. react中如何加载异步组件](#3. react中如何加载异步组件)
  • [4. shouldComponentUpdate有什么用](#4. shouldComponentUpdate有什么用)
  • [5. state中值的不可变性](#5. state中值的不可变性)
  • [6. HOC和Render Props代码复用和逻辑分离](#6. HOC和Render Props代码复用和逻辑分离)
  • [7. redux](#7. redux)
  • 8.React-Redux
  • [9. react-reducx异步action](#9. react-reducx异步action)

1. Portals将子组件渲染到父组件以外的DOM节点上

在React中,Portals是一种机制,可以将子组件渲染到父组件以外的DOM节点上。通常情况下,React组件会被渲染到它们的父组件的DOM结构中,但有时我们希望将某个组件渲染到DOM结构的其他位置或根节点上,这时就可以使用Portals。

Portals的主要用途包括但不限于以下几点:

  1. 在应用中的任意位置渲染组件,无需将所有组件都嵌套在同一个层级结构中。
  2. 在模态框或弹出窗口等场景下,将组件渲染到最顶层的DOM节点上,确保它们处于最前面且不受其他布局影响。
  3. 在复杂布局下,将组件渲染到特定的DOM节点上,实现更灵活的布局控制。

举个例子,假设我们有一个模态框组件Modal,并希望将其渲染到页面的最顶层节点上,可以使用Portals实现:

js 复制代码
import { createPortal } from 'react-dom';

const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {
  render() {
    return createPortal(
      this.props.children,
      modalRoot
    );
  }
}

// 在父组件中使用Modal组件
class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, React!</h1>
        <Modal>
          <div>
            This is a modal content.
          </div>
        </Modal>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app'));

在上面的例子中,Modal组件使用Portals将其子组件渲染到了id为'modal-root'的DOM节点上,而不是直接嵌套在App组件内部。这样就实现了在页面的不同位置渲染组件的效果。

2. Context组件树中传递数据

React中的Context是一种全局状态管理的方法,它允许在组件树中传递数据,而不必一层层地手动传递props。通常用于在组件之间共享一些全局的信息。

Context包括两部分:Provider和Consumer。Provider提供数据并传递给子组件,而Consumer消费Provider提供的数据。

一个简单的例子是,在一个多层嵌套的组件结构中,如果需要在底层组件访问或修改顶层组件的状态数据,使用Context就会很方便。

js 复制代码
// 创建一个Context
const MyContext = React.createContext();

// 顶层组件提供数据
class ParentComponent extends React.Component {
  state = {
    message: 'Hello from ParentComponent!',
  };

  render() {
    return (
      <MyContext.Provider value={this.state.message}>
        <ChildComponent />
      </MyContext.Provider>
    );
  }
}

// 底层组件消费数据
const ChildComponent = () => {
  return (
    <MyContext.Consumer>
      {value => <p>{value}</p>}
    </MyContext.Consumer>
  );
}

// 渲染
ReactDOM.render(<ParentComponent />, document.getElementById('root'));

在此例中,ParentComponent提供了一个message数据,并通过MyContext.Provider传递给ChildComponent,ChildComponent通过MyContext.Consumer来消费并显示message的值。

在函数式组件中可以使用useContext来实现,具体可以看看这篇react中的context实现深层数据传递

3. react中如何加载异步组件

在 React 中加载异步组件通常使用 React.lazy() 方法。React.lazy() 接收一个函数,这个函数需要动态 import 这个组件,返回一个 Promise,React 将在组件加载完成后渲染它。

举例说明:

javascript 复制代码
import React, { Suspense } from 'react';

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

function App() {
  return (
    <div>
      <h1>异步组件加载示例</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <AsyncComponent />
      </Suspense>
    </div>
  );
}

export default App;

在上面的示例中,我们创建了一个 App 组件,通过 React.lazy() 方法加载了一个异步组件 AsyncComponent。在 Suspense 组件中,我们可以设置一个 fallback 属性,在组件加载完成前会显示该属性的内容。

需要注意的是,React.lazy() 目前只支持默认导出的组件,所以被异步加载的组件需要使用默认导出。

4. shouldComponentUpdate有什么用

shouldComponentUpdate是React组件中一个重要的生命周期函数,用于控制组件的更新频率。当组件的props或state发生变化时,React会调用shouldComponentUpdate方法来判断是否需要重新渲染组件。如果shouldComponentUpdate返回false,React则会阻止组件的更新,从而提高性能。

举个例子,假设有一个列表组件List,当用户点击某个按钮时,会向列表中添加一个新的项。但是,如果列表中已经包含相同的项,则不需要更新列表。在这种情况下,我们可以通过shouldComponentUpdate来判断新的项是否已经存在于列表中,如果存在则返回false,否则返回true。

js 复制代码
class List extends React.Component {
  shouldComponentUpdate(nextProps) {
    // 检查新的项是否已经存在于列表中
    if (this.props.items.includes(nextProps.newItem)) {
      return false;
    }
    return true;
  }

  render() {
    return (
      <ul>
        {this.props.items.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    );
  }
}

在上面的例子中,当shouldComponentUpdate返回false时,新的项不会被添加到列表中,从而避免不必要的组件更新。这样可以减少组件的渲染次数,提高性能。

5. state中值的不可变性

在React中,保持原有state值的不可变性是为了确保组件的可预测性和性能。如果直接修改state中的值,可能会导致不可预测的行为,因为React可能无法正确地检测到state的变化,进而无法正确地重新渲染组件。

通过确保state的不可变性,可以减少出现bug的可能性,因为我们可以清晰地追踪state的变化。此外,React使用了一个称为"浅比较"的机制来检测state的变化,如果直接修改state中的值,可能会导致React无法正确地检测到state的变化,导致组件不会重新渲染,从而影响性能。

因此,在使用setState更新state时,应该始终创建一个新的对象或数组,而不是直接修改原始state,以确保state的不可变性和组件的可靠性。

举例来说,假设有一个组件的state中有一个数组:

js 复制代码
this.state = {
  numbers: [1, 2, 3, 4, 5]
}

如果我们想将数组中第一个元素改为6,正确的做法是先创建一个新的数组,然后再更新state:

js 复制代码
const newNumbers = [...this.state.numbers];
newNumbers[0] = 6;

this.setState({numbers: newNumbers});

如果直接修改state中的数组,例如:

js 复制代码
this.state.numbers[0] = 6;
this.setState({numbers: this.state.numbers});

这种方式会直接修改原有的state中的数组,可能导致React无法正确检测到state的变化,从而导致界面没有得到正确的更新。

这是因为React在进行state更新时,会进行引用比较来确定是否需要重新渲染组件。如果直接修改state中的数组而没有保持其不可变性,即没有创建一个新的数组来替换原有的数组,React可能会认为引用未发生改变,从而不会触发重新渲染。这样就会导致界面没有得到正确的更新,因为实际上state中的数据已经发生了变化,但React并没有正确检测到这一点。因此,在React中,强调保持原有值的不可变性是为了确保React能够正确地检测到state的变化并触发组件的重新渲染。

因此我们在操作数组时,要使用返回新数组的方法

常用的JS数组方法中,会返回新数组的方法有:

  1. map()
  2. filter()
  3. concat()
  4. slice()
  5. flat()
  6. reduce()

会修改原数组的方法有:

  1. pop()
  2. push()
  3. shift()
  4. unshift()
  5. splice()
  6. reverse()
  7. sort()

6. HOC和Render Props代码复用和逻辑分离

HOC(Higher Order Component)和Render Props都是React中常用的模式,用于组件之间的代码复用和逻辑分离。

HOC是一个函数,接受一个组件作为参数并返回一个新的增强组件。通过HOC可以将通用的逻辑封装到一个函数中,然后在多个地方重复使用。例如,下面是一个HOC,用于给组件添加loading状态:

js 复制代码
const withLoading = (WrappedComponent) => {
  return class WithLoading extends React.Component {
    state = {
      loading: true
    }

    componentDidMount() {
      setTimeout(() => {
        this.setState({ loading: false });
      }, 2000);
    }

    render() {
      return this.state.loading ? <div>Loading...</div> : <WrappedComponent {...this.props} />;
    }
  }
}

Render Props则是通过组件的props将函数传递给子组件,子组件可以调用该函数来获取值或者逻辑。通过Render Props可以更加灵活地共享代码逻辑。下面是一个Render Props的示例,用于共享鼠标坐标:

js 复制代码
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.children(this.state)}
      </div>
    );
  }
}

const App = () => (
  <MouseTracker>
    {({ x, y }) => (
      <div>
        <h1>Mouse coordinates:</h1>
        <p>X: {x}, Y: {y}</p>
      </div>
    )}
  </MouseTracker>
);

在上面的例子中,MouseTracker组件通过props.children将函数传递给子组件,子组件可以通过调用该函数获取到鼠标坐标的值。

7. redux

Redux是一个用于JavaScript应用程序的开源状态管理库,它被设计为一个可预测状态容器,使得应用的状态管理更加可控并且易于调试。

Redux的主要用途是管理应用程序的状态,例如用户的登录状态、页面的加载状态以及组件之间的通信等。通过Redux,我们可以将应用程序的状态统一管理在一个容器中,通过定义action、reducer和store来管理数据的流动,从而确保状态的一致性和可控性。

举例说明,我们可以创建一个简单的计数器应用程序来演示Redux的用法:

首先,我们定义一个action,例如增加计数器的动作:

javascript 复制代码
const increment = () => {
  return {
    type: 'INCREMENT'
  };
};

然后,我们定义一个reducer来处理action,并更新应用程序的状态:

javascript 复制代码
const counterReducer = (state = 0, action) => {
  switch(action.type) {
    case 'INCREMENT':
      return state + 1;
    default:
      return state;
  }
};

接着,我们创建一个Redux store,并将reducer注册到store中:

javascript 复制代码
import { createStore } from 'redux';

const store = createStore(counterReducer);

最后,在应用程序中,我们可以通过dispatch一个action来更新应用的状态,并通过getState方法获取最新的状态值:

javascript 复制代码
store.dispatch(increment());
console.log(store.getState()); // 输出1

通过以上示例,我们可以看到Redux通过action和reducer来管理应用程序的状态,使得状态的变化变得可预测和容易管理。
Action Reducer State View

8.React-Redux

React-Redux 是一个用于 React 应用程序的 JavaScript 库,用于管理应用程序状态并帮助在组件之间传递数据。React-Redux 绑定了 Redux 的状态管理功能与 React 组件,使得在 React 应用程序中使用 Redux 变得更加简单和高效。

使用 React-Redux 有以下几个主要的优势:

  1. 提供了一个统一的状态管理中心,使得应用程序的状态更加集中化和易于维护。
  2. 可以避免将状态传递多个组件层级,通过 React-Redux 提供的 connect 函数,可以直接从状态管理中心获取数据。
  3. 帮助优化组件的性能,可以避免不必要的渲染和数据更新。

使用 React-Redux 的基本步骤如下:

  1. 安装 React-Redux:在项目中安装 react-redux 包,可以使用 npm 或者 yarn 进行安装。
  2. 创建 Redux store:在项目中创建 Redux store,并定义相关的 reducer、action 和 middleware。
  3. 使用 Provider:在根组件中使用 Provider 组件,并将创建的 Redux store 作为 props 传递给 Provider。
  4. 使用 connect 函数:在需要访问 Redux store 数据的组件中,使用 connect 函数来连接组件和 Redux store,将状态作为 props 传递给组件。

举例说明:

js 复制代码
// 定义 reducer
const counterReducer = (state = 0, action) => {
  switch(action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
};

// 创建 Redux store
const store = createStore(counterReducer);

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

// 连接组件和 Redux store
const mapStateToProps = state => ({
  count: state
});

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

const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);

// 在根组件中使用 Provider
const App = () => (
  <Provider store={store}>
    <ConnectedCounter />
  </Provider>
);

在以上的示例中,我们创建了一个简单的计数器应用程序,使用了 React-Redux 进行状态管理。通过 connect 函数连接了 Counter 组件和 Redux store,并使用 Provider 在根组件中传递了 Redux store。当点击 Increment 和 Decrement 按钮时,会分别触发对应的 action,从而更新组件中的状态。

9. react-reducx异步action

在React-Redux中使用异步action通常需要借助中间件,最常用的中间件是redux-thunk。redux-thunk允许action创建函数返回一个函数而不是一个action对象,这样就可以在返回的函数中进行异步操作。

下面是一个使用redux-thunk的异步action的示例:

javascript 复制代码
// actions.js
const fetchPostsRequest = () => {
  return {
    type: 'FETCH_POSTS_REQUEST'
  }
}

const fetchPostsSuccess = (posts) => {
  return {
    type: 'FETCH_POSTS_SUCCESS',
    payload: posts
  }
}

const fetchPostsError = (error) => {
  return {
    type: 'FETCH_POSTS_ERROR',
    payload: error
  }
}

export const fetchPosts = () => {
  return (dispatch) => {
    dispatch(fetchPostsRequest());
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => {
        dispatch(fetchPostsSuccess(data));
      })
      .catch(error => {
        dispatch(fetchPostsError(error));
      });
  }
}

// reducer.js
const initialState = {
  posts: [],
  loading: false,
  error: null
}

const postsReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'FETCH_POSTS_REQUEST':
      return {
        ...state,
        loading: true
      }
    case 'FETCH_POSTS_SUCCESS':
      return {
        ...state,
        loading: false,
        posts: action.payload,
        error: null
      }
    case 'FETCH_POSTS_ERROR':
      return {
        ...state,
        loading: false,
        error: action.payload
      }
    default:
      return state
  }
}

export default postsReducer;

在上面的示例中,fetchPosts函数是一个异步action创建函数,它返回一个函数,这个函数内部执行异步操作,获取数据后分发相应的成功或失败action。在reducer中根据不同的action类型更新state。

需要注意的是,在创建store时要应用redux-thunk中间件,示例代码如下:

javascript 复制代码
// store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

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

export default store;

这样就可以在React组件中dispatch这个fetchPosts函数来获取数据,并在数据获取成功或失败后更新UI。

相关推荐
熊的猫2 分钟前
webpack 核心模块 — loader & plugins
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
速盾cdn9 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
四喜花露水41 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust1 小时前
css:基础
前端·css
帅帅哥的兜兜1 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
工业甲酰苯胺2 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
yi碗汤园2 小时前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称2 小时前
购物车-多元素组合动画css
前端·css