React — 原理面试题-持续更新

1.什么是React事件,什么是原生事件?两者的区别在哪儿?

  • React 事件: React 事件是经过封装和合成的,以保证在不同浏览器上的一致性。在使用 React 中的事件处理时,你会给 JSX 元素添加事件处理函数,比如 onClickonChange 等,然后在事件处理函数中处理相应的逻辑。React 事件的处理方式提供了一定程度的抽象,使得开发者可以更加方便地处理事件和跨浏览器的兼容性
function handleClick() {
  console.log('Button clicked');
}

<button onClick={handleClick}>Click me</button>
  • 原生事件是指由浏览器原生提供的事件,如 clickchangemouseover 等。在传统的 Web 开发中,我们通过 JavaScript 直接操作 DOM 元素并为其绑定原生事件处理程序来实现交互行为。
document.getElementById('myButton').addEventListener('click', function() {
  console.log('Button clicked');
});

区别:

  1. 合成事件系统:React 事件是经过合成的,React 通过事件委派(event delegation)的方式来管理事件,而不是直接将事件绑定到每个 DOM 元素上。这种方式可以提高性能,同时减少内存占用。
  2. 跨浏览器兼容性:React 事件处理封装了底层的浏览器差异,使得开发者无需关心不同浏览器的事件兼容性问题。
  3. 事件命名:React 使用驼峰式命名来定义事件名,如 onClickonChange,而原生事件则使用全小写的方式,如 clickchange
React 中的事件委派是指 React 将事件处理逻辑委托给组件的共同祖先(根组件),而不是直接在每个组件上添加事件监听器。这意味着 React 在整个组件树中只添加了一个事件监听器,而不是每个组件都有自己的事件监听器。
当用户触发事件时,React 会在 DOM 树中找到最近的共同祖先,并在该节点上触发事件处理函数。然后,React 会使用事件冒泡(event bubbling)的机制将事件传播到组件树中的每个组件,并调用相应的事件处理函数。
React 事件委派的优点包括:
性能优化:减少了事件监听器的数量,提高了性能。相比每个组件都添加事件监听器,只有一个共同祖先添加事件监听器的方式更加高效。
简化事件管理:不需要为每个组件都添加事件监听器,减少了事件管理的复杂性。当组件被添加、移除或更新时,不需要手动管理事件监听器。
更少的内存占用:只有一个事件监听器,减少了内存占用。

 

2.React的diff算法

React 的 Virtual DOM 和 diff 算法是其性能优势的关键所在。在 React 中,当 state 或 props 发生变化时,React 会通过 diff 算法比较新旧 Virtual DOM 树的差异,并只更新实际变化的部分,而不是重新渲染整个页面。

React 的 diff 算法主要包括以下几个步骤:

  1. 生成 Virtual DOM 树:当组件状态发生变化时,React 会重新构建 Virtual DOM 树。

  2. Diffing 算法:React 使用 diff 算法比较新旧 Virtual DOM 树的差异。这一过程包括两个阶段:

    • 深度优先搜索:React 会递归地遍历新旧节点,找出差异。如果节点类型不同,直接替换;如果节点类型相同,继续比较子节点。

    • 对比子节点:React 会对比新旧节点的子节点列表,找出需要更新、删除或新增的节点。

  3. 更新 DOM:根据 diff 的结果,React 会计算出最小的变更,然后批量更新 DOM,以尽量减少对实际 DOM 的操作次数。

React 的 diff 算法的优势在于减少了不必要的 DOM 操作,提高了性能。通过只更新变化的部分,React 能够更高效地处理大型应用程序中的 UI 变化,同时保持页面的响应速度。

需要注意的是,尽管 React 的 diff 算法在大多数情况下表现良好,但也有一些特殊情况可能会导致性能问题,比如列表中的子元素顺序变化较大时。针对特定场景,开发者也可以通过一些手动优化措施来提升性能。

 

3.React的diff算法跟Vue的diff算法的区别?

React 的 diff 算法是基于 Fiber 架构的 Reconciliation(对账) 算法,采用深度优先的递归比较方式,使用 Key 属性和双端比较来处理 Virtual DOM 树的差异。

Vue 使用双端渲染和 Virtual DOM 的差异比较算法,通过节点索引和差异比较实现快速更新操作。

React 更灵活和精确,Vue 更简洁高效。选择取决于具体需求和偏好

 

4.React的生命周期?废弃了哪些?可替代的有哪些?生命周期到现在的一个历程?

在 React 16.3 版本之前,React 组件的生命周期方法包括以下几个阶段:

  1. Mounting(挂载阶段):

    • constructor()
    • componentWillMount()(已废弃)
    • render()
    • componentDidMount()
  2. Updating(更新阶段):

    • componentWillReceiveProps()(已废弃)
    • shouldComponentUpdate()
    • componentWillUpdate()(已废弃)
    • render()
    • componentDidUpdate()
  3. Unmounting(卸载阶段):

    • componentWillUnmount()
  4. Error Handling(错误处理阶段):

    • componentDidCatch()

在 React 16.3 版本中,一些生命周期方法被标记为废弃,主要是因为它们可能会导致一些潜在的问题,或者在未来版本中可能会被移除。这些废弃的生命周期方法包括:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

替代方案包括:

  • getDerivedStateFromProps():用于替代 componentWillReceiveProps 和 componentWillUpdate,允许根据 props 的变化来更新组件的 state。
  • getSnapshotBeforeUpdate():用于替代 componentWillUpdate,允许在更新前获取 DOM 的快照信息。

React 16.8(引入了 Hooks)

  • 引入了 useEffect 这一 Hook,用于替代类组件中的生命周期方法,以处理组件的副作用操作。
  • useEffect 可以在函数组件中执行副作用操作,并且可以在组件渲染后进行一些操作,类似于类组件中的生命周期方法。
  • useEffect 也可以返回一个清除函数,用于清理副作用操作,避免内存泄漏和其他问题。

useEffect 主要替代了类组件中的 componentDidMount, componentDidUpdate, 和 componentWillUnmount 等生命周期方法,使得组件的副作用操作更加灵活和易于管理。

 

5.React常用的hook函数有哪些?如何自定义hook函数?

  • useState:用于在函数组件中添加状态管理能力。
const [count, setCount] = useState(0);
 <button onClick={() => setCount(count + 1)}>{count}</button>
  • useEffect:处理副作用操作,比如数据获取、订阅等。
useEffect(() => {
  // 这里的代码在组件挂载时执行

  return () => {
    // 这里的代码在组件卸载时执行
  };
}, []); // 空数组意味着没有依赖,只在组件挂载和卸载时执行
  • useContext:让你在函数组件中使用 React 的 Context。
在 React 中,Context 是一种用于在组件之间共享数据的方法,而无需通过组件树的逐层传递 props。它可以帮助我们避免在多层嵌套的组件中通过 props 一层层地传递数据,特别是对于应用中许多组件都需要某些相同的数据时很有用。
使用 Context,可以创建一个“全局”数据存储,使得所有子组件都可以直接访问这些数据,而不必通过中间组件来传递。这在某些情况下能够简化组件之间的通信并提高代码的可维护性。
const ThemeContext = React.createContext('light');
// 在根组件中提供共享的数据
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 在子组件中使用共享的数据
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme }}>I am styled by theme context!</button>;
}
  • useReducer:类似于 Redux 中的 reducer,用于复杂的状态管理。
// 定义 reducer 函数
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};

// 初始状态
const initialState = { count: 0 };

// 使用 useReducer
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}
  • useRef:获取 DOM 元素的引用或者保存任意可变值。
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
  • useMemo:用于性能优化,避免不必要的计算。
useMemo 的作用
1.性能优化:useMemo 可以缓存计算结果,在依赖项不变的情况下避免重复计算,从而提高性能。
2.避免不必要的渲染:当组件重新渲染时,如果某个值是通过 useMemo 缓存的,且依赖项没有发生变化,那么这个值不会重新计算,避免不必要的渲染。
function ExpensiveCalculation({ value }) {
  // 使用 useMemo 缓存计算结果
  const result = useMemo(() => {
    console.log('Calculating...');
    return value * 2;
  }, [value]); // 依赖项为 value

  return <div>Result: {result}</div>;
}

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ExpensiveCalculation value={count} />
    </div>
  );
}
  • useCallback:用于性能优化,避免不必要的回调函数重复创建。
function ClickCounter({ onClick }) {
  return <button onClick={onClick}>Click Me</button>;
}

function App() {
  const [count, setCount] = useState(0);

  // 使用 useCallback 缓存回调函数
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]); // 依赖项为 count

  return (
    <div>
      <p>Count: {count}</p>
      <ClickCounter onClick={handleClick} />
    </div>
  );
}
  • useLayoutEffect:类似于 useEffect,但会在 DOM 变更之后同步触发效果。
useLayoutEffect 是 React 提供的一个 Hook,与 useEffect 类似,用于在组件渲染到 DOM 之后执行副作用。但是,useLayoutEffect 会在所有 DOM 变更之后同步调用副作用函数,以确保 DOM 变更同步更新。
适合使用场景
1.需要在 DOM 更新后立即执行操作,如测量 DOM 元素的尺寸、位置等。
2.需要在布局计算之后立即执行操作,以避免闪烁或布局问题。
function WidthMeasure() {
  const [width, setWidth] = useState(0);
  const divRef = useRef();

  useLayoutEffect(() => {
    setWidth(divRef.current.clientWidth);
  }, []); // 空依赖项,只在组件挂载和更新时执行

  return (
    <div>
      <div ref={divRef}>Measure my width</div>
      <p>Width: {width}px</p>
    </div>
  );
}

 

6.hook函数的优缺点?

优点:

  1. 代码复用:Hook 函数使得在函数组件中重用状态逻辑变得更容易。

  2. 组合性:Hook 函数之间可以自由组合,使得逻辑组织更加灵活。

  3. 清晰简洁:将相关逻辑分离到不同的函数中,使得代码清晰易懂。

  4. 无需关注 this 指针:避免了类组件中常见的 this 指针问题。

  5. 轻量化:相比类组件,函数组件通常更轻量,性能更好。

缺点:

  1. 学习曲线:需要一定时间来理解和掌握 Hook 函数的概念。

  2. 破坏性改变:改变了传统的 React 编程习惯,可能需要适应和转变思维。

  3. 限制:一些生命周期方法和特性在函数组件中无法直接使用。

  4. 闭包陷阱:需要注意闭包问题,避免意外行为。

 

7.React组件怎么做事件代理?

事件代理(Event Delegation是一种利用事件冒泡机制来处理事件的技术。在浏览器中,当一个元素上触发了某个事件时(比如点击事件),这个事件会沿着 DOM 树向上传播,直到根节点。利用事件冒泡,我们可以在父元素上监听事件,然后通过判断具体触发事件的子元素来执行相应的逻辑,从而实现事件代理。

在 React 中,事件代理的原理与浏览器中的事件冒泡机制是一致的。当子组件上的事件被触发时,事件会一层层向上传播,直至父组件或更高层级的祖先组件。通过在父组件上设置事件监听,并在事件处理函数中判断触发事件的具体元素,就能实现事件代理。

具体步骤如下:

  1. 在父组件上添加事件监听函数。
  2. 事件触发后,事件会沿着 DOM 树向上传播。
  3. 在事件处理函数中,可以通过 event.target 获取触发事件的具体元素。
  4. 根据 event.target 的信息,执行相应的逻辑。

通过这种方式,可以减少在子组件上编写大量重复的事件处理函数,提高代码的可维护性和性能表现。同时,利用事件代理还可以动态地管理子组件中的事件,更灵活地处理用户交互。

 

8.React的高阶组件(HOC)是什么?

React 的高阶组件(Higher-Order Component,HOC)是一种用于复用组件逻辑的高级技术。它本质上是一个函数,接受一个组件作为输入,并返回一个新的组件。通过使用高阶组件,可以在不修改现有组件代码的情况下,添加额外的功能、状态或逻辑。

高阶组件的基本特点和用法:

  1. 接受组件作为参数:高阶组件接受一个组件作为参数,通常以组件作为参数的函数形式实现。

  2. 返回新的组件:高阶组件内部会对传入的组件进行包装或者修改,最终返回一个新的组件。

  3. 复用逻辑:通过高阶组件,可以将一些通用的逻辑、状态管理、数据获取等功能提取出来,并应用到多个组件中,实现逻辑的复用。

  4. 可组合:可以通过组合多个高阶组件,实现更复杂的功能扩展和逻辑复用。

  5. 与原始组件解耦:通过高阶组件,可以将与组件逻辑无关的功能(例如数据获取、权限控制等)与原始组件逻辑分离,提高了组件的复用性和可维护性。

使用高阶组件的常见场景包括但不限于:状态管理、数据获取、条件渲染、权限控制、事件处理等

 

9.React.createClass和 extends Component的区别?

  1. React.createClass:
    • React.createClass 是 React 早期版本提供的一种创建组件的方式。
    • 使用 React.createClass 创建组件时,可以直接定义组件的配置对象,其中包括组件的状态、生命周期方法等。
    • 不需要手动绑定 this,因为在 React.createClass 中,函数会自动绑定到实例上。
    • 不支持 ES6 类的特性,比如无法使用类属性语法(class properties)等新特性。
const MyComponent = React.createClass({
  render: function() {
    return <div>Hello, World!</div>;
  }
});
  1. extends Component:
    • extends Component 是 ES6 类的语法,通过继承 React.Component 类来创建 React 组件。
    • 使用 ES6 类语法创建组件更符合现代 JavaScript 的标准,也更容易理解和维护。
    • 需要手动绑定 this,或者使用箭头函数来避免手动绑定问题。
    • 支持 ES6 类的特性,可以使用更多现代 JavaScript 的语法特性。
class MyComponent extends React.Component {
  render() {
    return <div>Hello, World!</div>;
  }
}

 

10.React根据什么判断什么时候重新渲染组件?

在 React 中,组件何时重新渲染是由 React 的 Virtual DOM 和 Reconciliation(对账) 来决定的。React 使用 Virtual DOM 来跟踪页面上的真实 DOM 树,并使用协调算法来确定何时以及如何更新组件。

当组件的状态(state)或属性(props)发生变化时,React 会触发组件的重新渲染。具体来说,React 对比前后两次渲染所生成的 Virtual DOM 树,找出两者之间的差异(Diff),然后只更新必要的部分到真实 DOM 中,以尽量提高性能和效率。

以下是一些触发组件重新渲染的情况:

  1. setState() 方法被调用:当调用组件的 setState() 方法时,React 会重新计算组件的 Virtual DOM 树,并与之前的 Virtual DOM 进行对比,从而确定需要更新的部分。

  2. props 改变:当父组件传递给子组件的 props 发生变化时,子组件会重新渲染以反映最新的 props 值。

  3. forceUpdate() 方法被调用:通过调用组件的 forceUpdate() 方法可以强制组件重新渲染,不管组件的状态或属性是否有变化。

  4. 父组件重新渲染:如果一个组件的父组件重新渲染,那么子组件也会相应地进行重新渲染。

值得注意的是,React 会尽量减少不必要的重新渲染,因此在实际开发中,我们可以通过 shouldComponentUpdate 生命周期方法或 PureComponent、React.memo 等方式来优化组件的性能,避免不必要的渲染。

 

11.React可以在哪个生命周期访问DOM,在哪个时机访问Ref?

访问 DOM: 在 React 中,通常应该遈避免直接操作 DOM,因为 React 采用 Virtual DOM 的方式管理页面渲染。但是,如果确实需要访问真实的 DOM 元素,可以在组件的以下生命周期方法中进行:

  • componentDidMount():在组件挂载后立即调用。可以在这个生命周期方法中访问和操作 DOM 元素,执行初始化操作等。
  • componentDidUpdate(prevProps, prevState):在组件更新后被调用。可以在此方法中根据更新后的 props 或 state 来访问或操作 DOM 元素。

访问 Ref: Ref 是用于访问真实 DOM 节点或 React 组件实例的一种方式。在 React 中,可以在以下时机访问 Ref:

  • 使用 Ref:可以componentDidMount()componentDidUpdate() 或事件处理程序中访问 Ref。通过 Ref 可以获取到对应的 DOM 元素或组件实例。

useEffect 中可以访问 DOM,但需要注意以下几点:

  • 初次渲染时访问 DOM:在 useEffect 中,如果不传递第二个参数(依赖数组),则 useEffect 的回调函数会在每次组件更新后都执行。在首次渲染时,DOM 已经被挂载,因此可以在这种情况下访问 DOM。
  • 依赖变化时访问 DOM:如果在 useEffect 中传递了依赖数组(第二个参数),则 useEffect 的回调函数会在依赖变化时执行。在这种情况下,如果依赖的状态或属性改变导致 useEffect 重新执行,那么可以在useEffect 中访问更新后的 DOM。

 

12.Prop是什么?state是什么?state和Prop的区别?

Props(属性):

  • Props 是从父组件向子组件传递数据的方式。它们类似于函数的参数,可以通过属性的方式传递给组件。
  • Props 是不可变的,子组件无法直接修改由父组件传递的 props。
  • 在父组件中使用子组件时,可以通过在 JSX 中添加属性来传递给子组件,子组件可以通过 this.props 访问到这些属性。
import React from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends React.Component {
  render() {
    return <ChildComponent name="John" age={25} />;
  }
}

// ChildComponent.js
import React from 'react';

class ChildComponent extends React.Component {
  render() {
    console.log(this.props.name); // 输出 'John'
    console.log(this.props.age); // 输出 25
    return <div>{/* ... */}</div>;
  }
}

State(状态):

  • State 是组件内部的状态,用于控制组件的行为和展示。它可以随着组件交互或其他因素的改变而更新。
  • State 是可变的,可以通过 this.setState() 方法来更新组件的状态,并随之重新渲染组件。
  • 只有类组件可以拥有 state,函数组件可以使用 useState Hook 来拥有类似的状态管理能力。
import React, { useState } from 'react';

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

  const increaseCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increaseCount}>Increase Count</button>
    </div>
  );
};

区别:

  • 主要区别在于用途和作用范围
    • Props 用于从父组件向子组件传递数据,是只读的,子组件无法直接修改。
    • State 用于管理组件内部的状态,可以随着组件的交互或其他因素改变而更新,是可变的。
  • Props 是在组件外部定义和传递的,而 State 是在组件内部定义和管理的。

 

13.setState做了什么操作?异步还是同步?

当调用 React 组件的 setState 方法时,它会触发组件的重新渲染,并更新组件的状态setState 方法可以接受一个新的状态对象或一个返回新状态对象的函数作为参数。

在调用 setState 后,React 会将新的状态合并到当前状态中,并且异步地重新渲染组件。这意味着 React 可能会将多个 setState 调用合并成单个更新,以提高性能和避免不必要的重复渲染。因此,不能依赖于 setState 立即改变状态并获取最新的状态值。

虽然 setState 是异步的,但是 React 保证了在以下情况下可以获得更新后的状态:

  • 在生命周期方法(如 componentDidUpdateuseEffect)中访问组件的状态,可以获取到更新后的状态值。
  • 通过传递回调函数给 setState,可以在状态更新完成后执行一些操作,获取到更新后的状态值。
// 使用回调函数获取更新后的状态
this.setState({ count: this.state.count + 1 }, () => {
  console.log('Updated count:', this.state.count);
});

// 在生命周期方法中获取更新后的状态
componentDidUpdate(prevProps, prevState) {
  console.log('Previous count:', prevState.count);
  console.log('Current count:', this.state.count);
}

需要注意的是,由于 setState 是异步的,在某些情况下可能需要特殊处理,比如需要立即在更新后获取最新的状态值时,可以使用回调函数或 useEffect 来处理。

 

14.React为什么要校验Prop?

React 中校验 Props 的主要目的是为了确保组件被正确使用并且传入的数据是符合预期的。通过对 Props 进行校验,可以提高代码的可靠性、可维护性和可读性,帮助开发人员尽早发现潜在的问题并减少调试时间。

以下是 React 校验 Props 的几个主要原因:

  1. 数据完整性和类型安全:Props 校验可以确保组件所需的数据类型和结构是正确的,避免意外的数据类型错误或缺失。这有助于提高代码的稳定性和可靠性。

  2. 组件复用:通过校验 Props,可以明确指定组件所需的数据格式和限制条件,使其更具通用性和可复用性。其他开发人员在使用组件时能够更容易地理解如何正确传入数据。

  3. 提供清晰的接口:Props 校验定义了组件的接口规范,使得组件的用法更加清晰明了。开发人员在调用组件时可以清晰地了解需要传入哪些数据,以及这些数据的约束条件。

  4. 调试和错误检测:当传入的 Props 不符合校验规则时,React 会在开发环境下发出警告信息,帮助开发人员快速定位问题并进行修复,从而提高代码质量和可维护性。

React 提供了 prop-types 库来实现 Props 校验,开发人员可以通过定义组件的 propTypes 属性来指定每个 Prop 的类型、是否必填等校验规则。

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
};

 

15.Redux是什么?描述一下Redux的工作流程?

Redux 是一个用于管理应用程序状态的 JavaScript 库,它可以与任何 JavaScript 应用一起使用,不仅限于 React。Redux 的核心概念是将应用程序的状态(state)和状态变更逻辑(reducers)统一管理,使得状态的变化可预测且易于跟踪。

Redux 的工作流程可以简单描述为以下几个步骤:

  1. Store:Redux 应用的状态被存储在一个单一的 JavaScript 对象中,称为 Store。这个 Store 包含了整个应用的状态树,并且是唯一的数据源。

  2. Action要更新应用状态,需要通过派发(dispatch)一个 Action 来描述发生的事件。Action 是一个包含 type 属性的普通 JavaScript 对象,用于描述事件的类型。例如:{ type: 'ADD_TODO', payload: {...} }

  3. Reducer:Reducer 是一个纯函数,接收先前的状态和一个 Action,返回新的状态。Reducer 即为状态的更新逻辑,负责根据 Action 更新状态。多个 Reducer 可以被组合成一个根 Reducer 来处理整个应用的状态变化。

  4. Store 更新:当派发一个 Action 后,Redux 会调用对应的 Reducer 来计算新的状态。然后,新的状态将替换旧的状态,并通知所有订阅了 Store 的组件进行更新。

  5. 连接 React:Redux 通常与 React 一起使用,通过 react-redux 库提供的 connect 函数将 React 组件连接到 Redux Store。这样,React 组件就可以从 Store 中获取状态,并在状态变化时重新渲染。

整个 Redux 的工作流程可以形成一个单向数据流:Action → Reducer → Store 更新 → 组件重新渲染。

 

16.Redux和VueX的区别?

  1. 生态系统:

    • Redux 是一个独立的状态管理库,可以与任何 JavaScript 应用一起使用,不仅限于 React。
    • Vuex 是专门为 Vue.js 开发的状态管理库,与 Vue.js 生态系统更紧密地集成在一起。
  2. 语法差异:

    • 在语法上,Redux 使用纯 JavaScript 和纯函数来描述状态和状态变更。
    • Vuex 利用了 Vue.js 的响应式系统,通过使用基于对象的 API 和 Vue 组件来描述状态和状态变更。
  3. 工作原理:

    • Redux 使用单一的 Store 来保存整个应用的状态,并且通过派发 Action、调用 Reducer 来更新状态。
    • Vuex 也使用单一的 Store,但利用了 Vue.js 的响应式系统来实现状态的更新和组件之间的通信。
  4. 工具和插件:

    • Redux 生态系统中有丰富的工具和中间件,例如 Redux DevTools 和 Redux Thunk,用于开发、调试和处理异步操作。
    • Vuex 也有类似的工具和插件,例如 Vuex DevTools,用于帮助开发者更好地理解和调试 Vuex 状态管理。

Redux 和 Vuex 在功能上是相似的,都旨在解决同样的问题:管理应用程序的状态。选择使用哪个库主要取决于你的项目需求和所使用的框架(React 还是 Vue.js)。如果你习惯使用 React,那么 Redux 可能更适合;如果你正在使用 Vue.js,那么 Vuex 是一个很好的选择。

 

17.React组件中的key如何理解?

当在 React 中使用列表(如数组映射到 JSX 元素)时,每个 JSX 元素都需要包含一个 "key" 属性。这个 "key" 属性的作用是帮助 React 识别每个列表项的唯一性,从而在进行更新时更高效地确定新增、删除或更新的元素。以下是更详细的解释:

  1. 唯一性:

    • 每个元素的 "key" 应该是唯一的,通常使用具有唯一标识符的数据字段,比如 ID。
    • 通过指定唯一的 "key",React 可以更准确地跟踪每个列表项的变化。
  2. 协助 React 进行重渲染:

    • 当列表中的元素发生变化时,React 需要确定哪些元素需要被添加、删除或更新。
    • 使用正确的 "key" 可以帮助 React 更快速地识别和处理这些变化,避免不必要的 DOM 操作,提高性能。
  3. 避免警告:

    • 如果列表中的元素没有指定 "key",React 会发出警告,因为缺少 "key" 可能导致 React 在更新时出现问题。
    • 提供正确的 "key" 可以帮助避免这些警告,并确保应用程序的稳定性与性能。
  4. 动态列表的优化:

    • 在动态生成的列表中,通过为每个元素分配稳定的 "key",可以帮助 React 更好地管理列表项的状态。
    • 如果列表项的顺序可能会改变,需要确保 "key" 是稳定的,不随列表项的位置变化而改变。

总的来说,"key" 属性在 React 中是一种重要的工具,用于帮助 React 更有效地管理和更新组件列表。正确使用 "key" 可以提高应用程序的性能和稳定性,特别是在涉及动态列表和列表项操作时。因此,在编写 React 组件时,请务必记得为列表中的每个元素提供一个唯一且稳定的 "key" 属性。

 

18.React组件通讯的方式有哪些?

Props(属性):在父组件中,通过props将数据传递给子组件。

//父组件

function ParentComponent() {
  const data = "Hello from parent";
  return <ChildComponent data={data} />;
}

//子组件

function ChildComponent(props) {
  return <div>{props.data}</div>;
}

State(状态):父组件可以通过props将状态作为数据传递给子组件。

//父组件
function ParentComponent() {
  const [count, setCount] = useState(0);
  return <ChildComponent count={count} />;
}
//子组件
function ChildComponent(props) {
  return <div>{props.count}</div>;
}

回调函数:子组件向父组件传递数据通常是通过回调函数的方式实现的。

//父组件
function ParentComponent() {
    const handleDataFromChild = (data) => {
        console.log("Data received from child component:", data);
    };

    return (
        <div>
            <p>Parent Component</p>
            <ChildComponent sendDataToParent={handleDataFromChild} />
        </div>
    );
}
//子组件
function ChildComponent(props) {
    const sendDataToParent = () => {
        const data = "Data from child component";
        props.sendDataToParent(data);
    };

    return (
        <div>
            <p>Child Component</p>
            <button onClick={sendDataToParent}>Send Data to Parent</button>
        </div>
    );
}

Context(上下文):使用React的Context API可以在祖先组件中定义数据,然后其后代组件可以通过Context来访问这些数据。

Refs(引用):通过使用ref,可以在父组件中引用子组件的实例,从而直接操作子组件的方法和数据。

//父组件
function ParentComponent() {
    const childRef = useRef(null);

    const sendDataToChild = () => {
        if (childRef.current) {
            childRef.current.receiveData("Data from parent");
        }
    };

    return (
        <div>
            <p>Parent Component</p>
            <button onClick={sendDataToChild}>Send Data to Child</button>
            <ChildComponent ref={childRef} />
        </div>
    );
}

//子组件
const ChildComponent = forwardRef((props, ref) => {
    const receiveData = (data) => {
        console.log("Data received in child component:", data);
    };

    useImperativeHandle(ref, () => ({
        receiveData
    }));

    return (
        <div>
            <p>Child Component</p>
        </div>
    );
});

Redux 或其他状态管理工具

 

19.React-Router有几种路由方式?路由中有哪些钩子函数?

路由方式:

  • BrowserRouter:基于 HTML5 History API 的路由方式,使用浏览器的 history 对象来管理 URL 和页面跳转。在 React 中可以通过 react-router-dom 库中的 BrowserRouter 组件来实现。
function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
          </ul>
        </nav>

        <Route path="/" exact component={Home} />
        <Route path="/about" component={About} />
      </div>
    </Router>
  );
}
  • HashRouter:基于 URL 的 hash(#)来管理路由,适用于不支持 HTML5 History API 的环境。同样可以通过 react-router-dom 库中的 HashRouter 组件来实现。

          使用方式与 BrowserRouter 类似,只是使用了 HashRouter 组件

路由的生命周期钩子函数:

  • useParams:useParams 钩子函数可以用来从 URL 中提取参数。当路由匹配成功时,可以使用 useParams 获取 URL 中的动态参数。
import { useParams } from 'react-router-dom';

function User() {
  let { id } = useParams();
  return <h2>User ID: {id}</h2>;
}
  • useHistory:useHistory 钩子函数提供了访问浏览器历史记录的方法,包括前进、后退、替换 URL 等操作。
import { useHistory } from 'react-router-dom';

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push('/home');
  }

  return <button onClick={handleClick}>Go Home</button>;
}
  • useLocation:useLocation 钩子函数可以获取当前 URL 的信息,包括 pathname、search、hash 等。
import { useLocation } from 'react-router-dom';

function PrintLocation() {
  let location = useLocation();
  return <div>The current URL is: {location.pathname}</div>;
}
  • useRouteMatch:useRouteMatch 钩子函数可以获取当前 URL 是否与指定的路径匹配,并提供匹配到的 URL 信息。
import { useRouteMatch } from 'react-router-dom';

function BlogPost() {
  let match = useRouteMatch('/blog/:id');
  // match.params.id 包含了 URL 中的动态参数
  return <div>Blog ID: {match.params.id}</div>;
}

 

posted on 2024-03-28 20:02  萬事順意  阅读(56)  评论(0编辑  收藏  举报