REACT

react中jsx规范

在 React 中使用 JSX 时,有一些规范和最佳实践可以帮助你编写更加清晰、一致和可维护的代码。以下是一些常见的 JSX 规范:

基本规则

  1. 标签必须闭合

    • 每个打开的标签都必须有一个对应的闭合标签。
    • 对于没有内容的自闭合标签,应该使用自闭合语法(例如 <img src="..." />)。
  2. 属性名称应为驼峰式命名

    • JSX 属性名称应该使用驼峰式命名法(camelCase),而不是 HTML 的短横线分隔命名法(kebab-case)。例如:className 而不是 classtabIndex 而不是 tab-index
  3. 属性值应使用引号

    • 属性值需要用双引号或单引号包围。推荐使用双引号以保持一致性。
  4. JSX 表达式需用花括号包裹

    • 在 JSX 中嵌入 JavaScript 表达式时,需要将表达式放在 {} 内。
  5. 条件渲染

    • 使用三元运算符或逻辑与操作符 (&&) 来进行简单的条件渲染。
    • 对于复杂的条件逻辑,建议提取到单独的函数中。
  6. 避免直接修改 DOM

    • 尽量避免在 JSX 中直接操作 DOM,而是通过状态和属性来控制组件的行为。

代码风格

  1. 文件扩展名

    • 使用 .jsx.tsx 作为文件扩展名,如果使用 TypeScript,则使用 .tsx
  2. 组件命名

    • 组件名称应该首字母大写,以便区分原生 DOM 标签。
    • 文件名应与组件名称一致,且采用 PascalCase(帕斯卡命名法)。
  3. 单一职责原则

    • 每个组件只负责一个功能,保持组件的简单性和可复用性。
  4. Props 和默认 Props

    • 使用 propTypes 或 TypeScript 接口来定义 props 类型。
    • 为组件提供默认 props 以增强灵活性。
  5. 注释

    • 使用多行注释来解释复杂逻辑或组件的目的。
    • 对于简单的 JSX 结构,可以使用内联注释。

代码格式化

  1. 缩进

    • 使用两个空格进行缩进。
    • 对于多行 JSX 元素,每个属性独占一行,并且对齐。
  2. 空格

    • 在属性和属性值之间保留一个空格。
    • 在 JSX 标签的右尖括号前不要有多余的空格。
  3. 换行

    • 如果 JSX 表达式超过了一定长度(通常为 80 到 100 个字符),则将其拆分成多行。
    • 对于长的 JSX 表达式,可以在属性较多时适当换行,使代码更易读。
  4. 自闭合标签

    • 自闭合标签的斜杠前不要有空格。

示例

import React from 'react';
import PropTypes from 'prop-types';

// 定义 prop 类型
const Button = ({ onClick, children }) => (
  <button
    type="button"
    onClick={onClick}
    className="btn btn-primary"
  >
    {children}
  </button>
);

Button.propTypes = {
  onClick: PropTypes.func.isRequired,
  children: PropTypes.node.isRequired,
};

export default Button;

在这个例子中,我们遵循了上述的一些规范:

  • 使用驼峰式命名法(className)。
  • 属性值用双引号包围。
  • 组件名称首字母大写。
  • 使用 propTypes 来定义 prop 类型。
  • 保持了良好的缩进和换行习惯。

遵循这些规范可以帮助你和其他开发者更容易地理解和维护代码。如果你所在的团队有特定的代码风格指南,也应该遵守那些指南。此外,使用像 ESLint 这样的工具可以帮助自动检查和修正代码风格问题。

React 事件机制

React 的事件机制是基于合成事件(SyntheticEvent)的,它提供了一层跨浏览器的事件处理系统。不需要担心不同浏览器之间的兼容性问题,因为 React 会将原生浏览器事件封装成一个统一的接口。

JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。

另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。

合成事件(SyntheticEvent)

标准化:React 的合成事件对象在所有浏览器中都有相同的接口,这样你就不用编写条件代码来处理不同的浏览器行为。 事件池:为了提高性能,React 使用了事件池来复用事件对象。一旦事件回调执行完毕,事件对象就会被释放回池中以供后续事件重用。因此,在异步访问事件对象时需要格外小心,因为它可能已经被清空或修改。

合成事件的目的如下

  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;

  • 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理事件的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象

react 类组件和函数组件的区别

函数组件(Function Components)

  1. 定义

    • 函数组件是普通的 JavaScript 函数,接收 props 作为参数,并返回 JSX 代码。
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
  2. 状态管理

    • 在 React 16.8 之前的版本中,函数组件不能管理自己的状态。但是自从引入了 Hooks(如 useState, useEffect 等),函数组件也可以拥有状态和生命周期能力。
    import React, { useState } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
    
  3. 性能

    • 函数组件通常比类组件更轻量,因为它们没有类组件的开销。React 可以更高效地处理函数组件。
  4. 代码简洁性

    • 函数组件的代码更加简洁,易于阅读和维护,尤其是在使用 Hooks 之后。

类组件(Class Components)

  1. 定义

    • 类组件是通过扩展 React.ComponentReact.PureComponent 来定义的。它们可以定义自己的状态和生命周期方法。
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    
  2. 状态管理

    • 类组件天然支持状态管理,可以通过 this.state 来定义和更新状态。
    class Example extends React.Component {
      constructor(props) {
        super(props);
        this.state = { count: 0 };
      }
    
      render() {
        return (
          <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={() => this.setState({ count: this.state.count + 1 })}>
              Click me
            </button>
          </div>
        );
      }
    }
    
  3. 生命周期方法

    • 类组件提供了丰富的生命周期方法,如 componentDidMountcomponentWillUnmountcomponentDidUpdate 等,这些方法允许你在组件的不同生命周期阶段执行特定的操作。
    class Example extends React.Component {
      componentDidMount() {
        console.log('Component did mount');
      }
    
      componentDidUpdate(prevProps, prevState) {
        console.log('Component did update');
      }
    
      componentWillUnmount() {
        console.log('Component will unmount');
      }
    
      render() {
        // ...
      }
    }
    
  4. 复杂性

    • 类组件相对复杂,包含更多的样板代码,如构造函数中的 super(props) 调用和事件处理器的绑定等。

选择使用哪种组件

  • 函数组件

    • 适用于简单的展示型组件,尤其是不需要管理状态或使用复杂生命周期方法的组件。
    • 使用 Hooks 后,函数组件可以实现与类组件相同的功能,代码更加简洁。
  • 类组件

    • 适用于需要管理状态和使用复杂生命周期方法的组件。
    • 在某些特定场景下,类组件仍然有其优势,例如在处理复杂的性能优化时。

随着 React Hooks 的引入,函数组件变得越来越强大,许多以前只能通过类组件实现的功能现在都可以通过函数组件来实现。因此,函数组件逐渐成为许多开发者的首选。然而,类组件在某些特定场景下依然有其独特的优势。

react 常用hooks

React Hooks 提供了一种在函数组件中使用状态和其他 React 特性的方式,使得函数组件可以拥有类组件的功能。以下是一些常用的 Hooks 及其应用场景:

1. useState

1. useState 的基本用法

useState 是一个函数,接收一个初始值作为参数,返回一个数组,包含两个元素:

  • 当前状态值:状态的当前值。
  • 状态更新函数:用于更新状态的函数。
const [state, setState] = useState(initialValue);
import React, { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

2. useState 的注意事项

(1)状态更新是异步的

React 可能会批量处理多个状态更新以提高性能,因此状态更新是异步的。这意味着你不能依赖当前状态值来立即计算下一个状态值。

示例
const [count, setCount] = useState(0);

const handleClick = () => {
  setCount(count + 1); // 异步更新
  console.log(count); // 这里打印的是更新前的值
};
解决方法

如果需要基于前一个状态值更新状态,可以使用函数式更新:

setCount(prevCount => prevCount + 1);

(2)初始值只会在首次渲染时使用

useState 的初始值只在组件首次渲染时生效。如果初始值是通过复杂计算得到的,可以传递一个函数来延迟计算。

示例

const [state, setState] = useState(() => {
  const initialState = calculateInitialValue(); // 复杂计算
  return initialState;
});

(3)状态更新会触发组件重新渲染

每次调用 setState 更新状态时,组件会重新渲染。因此,避免不必要的状态更新可以提高性能。

示例
const [user, setUser] = useState({ name: 'John', age: 30 });

// 错误:即使值未变化,也会触发重新渲染
setUser({ name: 'John', age: 30 });

// 正确:只有值变化时才更新
if (user.name !== 'John' || user.age !== 30) {
  setUser({ name: 'John', age: 30 });
}

(4)多个状态变量的管理

可以在一个组件中使用多个 useState 来管理不同的状态变量。

示例
const [name, setName] = useState('John');
const [age, setAge] = useState(30);

(5)状态是独立的

每个组件实例的状态是独立的,即使多个组件使用相同的 useState,它们的状态也不会共享。

示例
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function App() {
  return (
    <div>
      <Counter /> {/* 独立的状态 */}
      <Counter /> {/* 独立的状态 */}
    </div>
  );
}

3. 面试常见问题

(1)useState 和类组件的 this.state 有什么区别?
  • useState 是函数组件中用于管理状态的 Hook,而 this.state 是类组件中用于管理状态的方式。
  • useState 更简洁,不需要写类组件和生命周期方法。
  • useState 的状态更新是异步的,而类组件的 this.setState 也支持异步更新。

(2)如何避免不必要的重新渲染?
  • 使用 React.memo 对组件进行优化。
  • 使用 useCallbackuseMemo 缓存函数和值。
  • 避免在渲染函数中进行昂贵的计算。

(3)useState 的初始值可以是一个函数吗?

可以。如果初始值需要通过复杂计算得到,可以传递一个函数来延迟计算。

示例
const [state, setState] = useState(() => {
  return calculateInitialValue();
});

(4)useState 的状态更新是同步还是异步的?

useState 的状态更新是异步的。React 可能会批量处理多个状态更新以提高性能。


(5)如何实现一个计数器组件?

这是一个经典的面试题,考察对 useState 的基本使用。

示例
import React, { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

4. 总结

useState 是 React 中管理状态的核心 Hook,掌握它的使用和注意事项对开发至关重要。在面试中,除了基本用法,还需要注意以下几点:

  • 状态更新的异步性。
  • 初始值的延迟计算。
  • 避免不必要的重新渲染。
  • 函数式更新的使用场景。

通过结合实际的代码示例和面试题,可以更好地理解和掌握 useState

2. useEffect

  • 用途:用于执行副作用操作,如数据获取、订阅或手动更改 DOM。

  • 参数: 有两个

    效果函数 (effect function)

    这是一个函数,它包含了你想要执行的副作用代码。 该函数可以返回一个清理函数(cleanup function),这个清理函数会在组件卸载时执行,或者在下一次 useEffect 执行前执行。 依赖数组 (dependency array) 这是一个可选的数组,用来指定哪些变量的变化会触发 useEffect 的重新执行。 如果依赖数组为空 [],则 useEffect 只会在组件挂载和卸载时执行如果省略依赖数组,则 useEffect 会在每次渲染后执行

  • 应用场景

    • 在组件挂载后执行某些操作(如数据获取)。
    • 在组件卸载前执行清理工作(如取消订阅)。
    • 在特定依赖项变化时执行副作用。
import React, { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, []); // 空数组表示只在挂载和卸载时运行

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return <div>{JSON.stringify(data)}</div>;
}

3. useContext

  • 用途:用于访问 React Context 中的数据。
  • 应用场景
    • 全局状态管理,如主题切换、用户认证信息等。
    • 避免通过 props 逐层传递数据。
import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

function ChildComponent() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <div style={{ background: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }}>
      <p>Current Theme: {theme}</p>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
    </div>
  );
}

4. useReducer

  • 用途:用于管理复杂的状态逻辑,类似于 Redux。
  • 应用场景
    • 管理多个相关状态。
    • 复杂的状态更新逻辑。
    • useContext 结合使用进行全局状态管理。
import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

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>
  );
}

5. useCallback

  • 用途:用于缓存函数实例,避免不必要的重新渲染。
  • 应用场景
    • 优化性能,减少子组件的重新渲染。
    • 传递给子组件的回调函数,确保引用不变。
import React, { useCallback } from 'react';

function ParentComponent({ children }) {
  const handleButtonClick = useCallback(() => {
    console.log('Button clicked');
  }, []);  // 空数组表示每次渲染都使用相同的函数引用

  return <ChildComponent onClick={handleButtonClick} />;
}

function ChildComponent({ onClick }) {
  return <button onClick={onClick}>Click me</button>;
}

6. useMemo

  • 用途:用于缓存计算结果,避免不必要的计算。
  • 应用场景
    • 计算密集型的操作,如复杂的计算、格式化数据等。
    • 保持某些值的引用不变,以便于比较或传递给其他组件。
import React, { useMemo } from 'react';

function ExpensiveComponent({ a, b }) {
  const expensiveValue = useMemo(() => {
    // 假设这是一个非常耗时的计算
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += a * b;
    }
    return result;
  }, [a, b]);  // 仅在 a 或 b 发生变化时重新计算

  return <div>Expensive Value: {expensiveValue}</div>;
}

7. useRef

  • 用途:用于访问 DOM 元素或保存任何可变信息。
  • 应用场景
    • 获取 DOM 元素的引用,如聚焦输入框、滚动到某个位置等。
    • 保存不需要触发重新渲染的值,如定时器 ID、动画帧 ID 等。
import React, { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
  const inputEl = useRef(null);

  useEffect(() => {
    inputEl.current.focus();
  }, []);

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={() => inputEl.current.focus()}>Focus the input</button>
    </>
  );
}

8. useLayoutEffect

  • 用途:与 useEffect 类似,但在所有 DOM 变更之后同步调用,主要用于读取布局并进行同步重绘。
  • 应用场景
    • 需要同步测量 DOM 节点尺寸或位置的场景。
    • 需要在浏览器绘制之前完成的操作。
import React, { useLayoutEffect, useState } from 'react';

function Example() {
  const [width, setWidth] = useState(window.innerWidth);

  useLayoutEffect(() => {
    function handleResize() {
      setWidth(window.innerWidth);
    }

    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return <div>Window width: {width}</div>;
}

9. useImperativeHandle

  • 用途:自定义使用 ref 时暴露给父组件的实例值。
  • 应用场景
    • 自定义组件的公开接口,控制父组件如何通过 ref 访问子组件。
import React, { useRef, useImperativeHandle } from 'react';

const FancyInput = React.forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));

  return <input ref={inputRef} ... />;
});

function App() {
  const fancyInputRef = useRef();

  return (
    <div>
      <FancyInput ref={fancyInputRef} />
      <button onClick={() => fancyInputRef.current.focus()}>
        Focus the input
      </button>
    </div>
  );
}

这些是 React 中一些常用的 Hooks 及其应用场景。通过合理使用这些 Hooks,你可以更好地管理状态、生命周期和副作用,从而使你的代码更加简洁和易于维护。

reack hooks解决了什么问题

React Hooks 解决了在函数组件中使用状态和其他 React 特性时遇到的一些常见问题。Hooks 的引入使得函数组件可以拥有类组件的功能,如状态管理、生命周期方法等,同时保持代码的简洁和可复用性。以下是 React Hooks 解决的主要问题:

1. 状态管理

  • 问题:在 React 中,只有类组件可以拥有状态(this.statethis.setState)。函数组件是无状态的,无法直接管理状态。
  • 解决方案useState Hook 允许你在函数组件中添加状态。
import React, { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2. 副作用处理

  • 问题:在类组件中,你需要使用生命周期方法(如 componentDidMountcomponentDidUpdatecomponentWillUnmount)来处理副作用(如数据获取、订阅或手动更改 DOM)。
  • 解决方案useEffect Hook 可以在函数组件中处理这些副作用,并且可以替代多个生命周期方法。
import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;

    // 清理函数
    return () => {
      document.title = 'React App';
    };
  }, [count]); // 仅当 count 发生变化时重新执行

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

3. 上下文管理

  • 问题:在类组件中,你需要通过 contextTypeContext.Consumer 来访问上下文值。
  • 解决方案useContext Hook 可以让你在函数组件中直接访问上下文值。
import React, { useContext } from 'react';
import MyContext from './MyContext';

function Example() {
  const value = useContext(MyContext);

  return <div>{value}</div>;
}

4. 自定义 Hook

  • 问题:在类组件中,很难将一些逻辑提取到可复用的部分。
  • 解决方案:你可以创建自定义 Hook 来封装和复用有状态逻辑。
// useFetch.js
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

然后在组件中使用这个自定义 Hook:

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

function DataFetcher({ url }) {
  const { data, loading, error } = useFetch(url);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default DataFetcher;

5. 条件逻辑

  • 问题:在类组件中,复杂的条件逻辑通常需要拆分到不同的生命周期方法中。
  • 解决方案useMemouseCallback 等 Hook 可以帮助你优化性能,避免不必要的计算和渲染。
import React, { useMemo } from 'react';

function ComplexComponent({ a, b }) {
  const result = useMemo(() => {
    // 复杂的计算
    return computeExpensiveValue(a, b);
  }, [a, b]);

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

6. 代码组织

  • 问题:类组件中的逻辑可能会变得非常复杂,难以理解和维护。
  • 解决方案:Hooks 使得函数组件更加简洁,逻辑更加集中,易于理解和测试。

总结

React Hooks 通过提供一种新的方式来使用状态和其他 React 特性,解决了函数组件的局限性,使得函数组件可以像类组件一样强大。这不仅简化了代码,还提高了代码的可复用性和可维护性。通过合理使用 Hooks,开发者可以更高效地构建复杂的用户界面。

组件通信的方式有哪些

⽗组件向⼦组件通讯: ⽗组件可以向⼦组件通过传 props 的⽅式,向⼦组件进⾏通讯

⼦组件向⽗组件通讯: props+回调的⽅式,⽗组件向⼦组件传递props 进⾏通讯,此 props 为作⽤域为⽗组件⾃身的函 数,⼦组件调⽤该函数,将⼦组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中

兄弟组件通信: 找到这两个兄弟节点共同的⽗节点,结合上⾯两种⽅式由⽗节点转发信息进⾏通信

跨层级通信: Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如当前认证的⽤户、主题或⾸选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过

发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引⼊event 模块进⾏通信

全局状态管理⼯具: 借助 Redux 或者 Mobx 等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态

React Hook 的使用限制有哪些?

React Hooks 的限制主要有两条:

不要在循环、条件或嵌套函数中调用 Hook

在 React 的函数组件中调用 Hook

那为什么会有这样的限制呢?Hooks 的设计初衷是为了改进 React组件的开发模式。在旧有的开发模式下遇到了三个问题

组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 及状态管理框架。

复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深,导致关联部分难以拆分。

人和机器都很容易混淆类。常见的有 this 的问题,但在 React 团

队中还有类难以优化的问题,希望在编译优化层面做出一些改进。这

三个问题在一定程度上阻碍了 React 的后续发展,所以为了解决这

三个问题,Hooks 基于函数组件开始设计。然而第三个问题决定了Hooks 只支持函数组件

那为什么不要在循环、条件或嵌套函数中调用 Hook 呢

因为 Hooks的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码里不是数组,是链表

这些限制会在编码上造成一定程度的心智负担,新手可能会写错,为了避免这样的情况,可以引入 ESLint 的 Hooks 检查插件进行预防。

setState 是同步的还是异步的

在 React 中,setState 方法是异步的。这意味着当你调用 setState 时,状态不会立即更新,而是会被放入一个队列中,React 会在稍后的某个时间点批量处理这些更新。这种设计有几个好处:

  1. 性能优化:通过批量处理状态更新,React 可以减少不必要的渲染次数。如果多个 setState 调用被合并成一次更新,那么组件只会重新渲染一次,而不是多次。

  2. 避免竞态条件:由于 setState 是异步的,它可以帮助避免一些竞态条件(race conditions)。例如,如果你在一个函数中连续调用 setState 多次,并且每次调用都依赖于前一次的状态值,那么异步更新可以确保你得到正确的状态值。

setState 的行为

  • 回调函数:你可以向 setState 传递一个回调函数,该函数将在状态更新并且组件重新渲染后执行。这个回调函数接收两个参数:新的状态和当前的 props。

    this.setState({ count: this.state.count + 1 }, () => {
      console.log('State updated and component re-rendered:', this.state.count);
    });
    
  • 第二个参数setState 还接受一个可选的第二个参数,这是一个回调函数,在状态更新并重新渲染之后执行。这个回调函数与第一个参数中的回调函数是等效的。

    this.setState((prevState, props) => ({ count: prevState.count + 1 }), () => {
      console.log('State updated and component re-rendered:', this.state.count);
    });
    
  • 函数形式:为了处理基于当前状态的更新,你可以将 setState 的第一个参数传递为一个函数,这个函数接收前一个状态和当前的 props 作为参数,并返回一个新的状态对象。

    this.setState((prevState, props) => ({
      count: prevState.count + 1
    }));
    

示例

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    // 异步更新
    this.setState({ count: this.state.count + 1 });

    // 如果你想立即获取更新后的状态,可以使用回调
    this.setState({ count: this.state.count + 1 }, () => {
      console.log('Updated state:', this.state.count); // 这里可能不会打印出预期的结果
    });

    // 或者使用函数形式来确保基于最新的状态
    this.setState((prevState, props) => ({
      count: prevState.count + 1
    }));

    // 打印当前状态(可能是旧的状态)
    console.log('Current state:', this.state.count);
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

在这个例子中,点击按钮会触发 increment 方法,该方法会调用 setState 来更新状态。由于 setState 是异步的,直接在 setState 调用后打印 this.state.count 可能不会显示最新的状态值。如果你需要访问最新的状态,应该使用 setState 的回调函数或者在 render 方法中处理。

useRef 作用用法

useRef 是 React 中的一个 Hook,它可以用来创建一个可变的引用对象(ref object),其 .current 属性被初始化为传递的参数(initialValue)。useRef 的主要用途包括:

  1. 访问 DOM 元素:可以用来直接获取到 DOM 节点。
  2. 保持任何可变值:类似于在类组件中使用实例属性来保存数据,但不会触发重新渲染。
  3. 避免闭包问题:在处理回调函数时,可以用来避免因闭包引起的过期状态或 props 问题。

基本用法

访问 DOM 元素

import React, { useRef, useEffect } from 'react';

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>
    </>
  );
}

在这个例子中,useRef 创建了一个 ref 对象,并将其附加到 <input> 元素上。点击按钮时,通过 inputEl.current.focus() 可以让输入框获得焦点。

保持任何可变值

import React, { useRef, useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  // 在每次渲染后更新 ref 的值
  useEffect(() => {
    prevCountRef.current = count;
  });

  return (
    <div>
      <p>Now: {count}, before: {prevCountRef.current}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

在这个例子中,useRef 用来存储前一次的计数值。每次计数器更新时,useEffect 会更新 prevCountRef.current 的值。这样就可以在不需要触发额外渲染的情况下,访问到之前的计数值。

避免闭包问题

import React, { useRef, useEffect } from 'react';

function Timer() {
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      console.log('Tick');
    }, 1000);
    intervalRef.current = id;

    // 清除定时器
    return () => clearInterval(intervalRef.current);
  }, []);

  return <div>Timer Component</div>;
}

在这个例子中,useRef 用来存储定时器 ID。当组件卸载时,useEffect 返回的清理函数会清除这个定时器。这有助于避免内存泄漏,同时解决了由于闭包导致的过期状态问题。

关键点

  • .current 属性useRef 返回的对象有一个 .current 属性,你可以自由地修改它,而不会触发重新渲染。
  • 持久性useRef 的值在组件的整个生命周期内是持久的,即使组件重新渲染,它的值也不会改变。
  • 不要依赖于 .current 进行渲染逻辑:因为 .current 的变化不会触发重新渲染,所以不应该在渲染逻辑中依赖它。

通过这些示例和说明,你应该能够理解 useRef 的基本用法及其在不同场景下的应用。

什么是受控组件和非控组件

在 React 中,表单元素可以分为两种类型:受控组件(Controlled Components)和非受控组件(Uncontrolled Components)。这两种组件的主要区别在于数据流的控制方式以及状态管理的方式。

受控组件 (Controlled Components)

  • 定义:受控组件是其值由 React 状态控制的表单元素。这意味着表单元素的值始终反映应用的状态,并且每次输入变化时都会触发状态更新。
  • 工作原理:当用户输入数据时,会触发一个事件处理器,该处理器更新 React 组件的状态。由于组件的状态被设置为表单元素的值,所以每次输入后,React 会重新渲染表单元素以显示最新的状态值。
  • 优点
    • 数据流清晰可控,易于追踪和验证。
    • 可以立即响应用户的输入并进行实时验证。
    • 易于实现复杂的交互逻辑,如条件性禁用或启用按钮等。
  • 示例代码
import React, { useState } from 'react';

function ControlledInput() {
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  return (
    <input
      type="text"
      value={value}
      onChange={handleChange}
      placeholder="Enter some text..."
    />
  );
}

export default ControlledInput;

在这个例子中,<input> 的值是由 value 状态决定的,而 onChange 事件处理函数则负责更新这个状态。

非受控组件 (Uncontrolled Components)

  • 定义:非受控组件是指其值不由 React 状态直接控制的表单元素。它们通常使用 ref 来访问 DOM 节点,从而获取表单数据。
  • 工作原理:非受控组件允许浏览器来控制表单元素的值。你可以在需要的时候通过引用(ref)来获取表单元素的值。
  • 优点
    • 对于简单的表单,使用起来更简单直观。
    • 不需要为每个状态改变都写事件处理函数。
    • 当你需要与遗留的第三方库集成时可能更加方便。
  • 缺点
    • 无法实时获取到表单的数据。
    • 难以实现复杂的表单验证逻辑。
  • 示例代码
import React, { useRef } from 'react';

function UncontrolledInput() {
  const inputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Input Value:', inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        ref={inputRef}
        placeholder="Enter some text..."
      />
      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledInput;

在这个例子中,我们使用 useRef 创建了一个 ref,并将其附加到 <input> 元素上。当提交表单时,我们可以通过 inputRef.current.value 获取输入框中的值。

选择使用哪种类型的组件取决于具体的需求。对于大多数现代 Web 应用来说,受控组件提供了更多的灵活性和更好的用户体验,因此更常用。然而,在某些特定情况下,非受控组件可能是更好的选择。

react 缓存组件

在React中,组件缓存是一种重要的性能优化手段。它可以帮助我们避免不必要的渲染,从而提升应用的响应速度和用户体验。以下是一些常用的React组件缓存方法和技术:

1. 使用 React.memo 进行浅比较

React.memo 是一个高阶组件(HOC),它可以用来包裹函数组件,并且默认情况下会对传入的 props 进行浅比较。如果 props 没有改变,那么组件就不会重新渲染。这是最简单直接的方法来防止组件因父组件更新而无谓地重新渲染。

const MyComponent = React.memo((props) => {
  // 组件逻辑
  return <div>{props.value}</div>;
});

如果你需要自定义比较逻辑,可以传递第二个参数给 React.memo,这是一个函数,用于比较前后的 props 和 state 是否相等。

const areEqual = (prevProps, nextProps) => {
  // 自定义比较逻辑
  return prevProps.value === nextProps.value;
};

const MyComponent = React.memo((props) => {
  // 组件逻辑
  return <div>{props.value}</div>;
}, areEqual);

2. 使用 useMemo 缓存计算结果

useMemo 可以帮助你记忆计算的结果。这对于那些耗时较长的计算特别有用,因为你可以确保只有当依赖项发生变化时才会重新进行计算。

import { useMemo } from 'react';

const MyComponent = ({ a, b }) => {
  const result = useMemo(() => {
    // 复杂计算
    return expensiveCalculation(a, b);
  }, [a, b]);

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

3. 使用 useCallback 缓存回调函数

当你向子组件传递回调函数时,每次父组件渲染都会生成新的函数引用,这可能会导致子组件也重新渲染。useCallback 可以帮助你固定住这个函数引用,除非它的依赖项发生了变化。

import { useCallback } from 'react';

const MyComponent = () => {
  const handleClick = useCallback(() => {
    console.log('Clicked!');
  }, []);

  return <ChildComponent onClick={handleClick} />;
};

4. 第三方库实现类似Vue的KeepAlive功能

虽然React官方没有提供类似于Vue中的<KeepAlive>组件,但是社区提供了许多解决方案来模拟这种行为。例如,react-activation 提供了<KeepAlive>组件,它可以在路由切换时保留组件的状态。

安装并使用react-activation:

npm i -S react-activation

然后在你的代码中引入并使用<KeepAlive>

import { KeepAlive, AliveScope } from 'react-activation';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

function App() {
  return (
    <Router>
      <AliveScope>
        <Switch>
          <Route path="/page1" render={() => <KeepAlive name="Page1"><Page1 /></KeepAlive>} />
          <Route path="/page2" render={() => <KeepAlive name="Page2"><Page2 /></KeepAlive>} />
        </Switch>
      </AliveScope>
    </Router>
  );
}

这里<AliveScope>是必须的根组件,它会管理所有被<KeepAlive>包裹的组件的状态。每个<KeepAlive>组件都需要一个唯一的name属性,这样就可以区分不同的缓存实例。

5. 手动控制缓存

有时候你可能想要更细粒度地控制哪些组件或数据被缓存。这时你可以考虑自己实现一个简单的缓存机制,比如使用一个全局对象或者上下文API来存储状态信息。

结论

选择哪种缓存策略取决于你的具体需求和应用结构。对于大多数情况,React.memouseMemouseCallback 已经足够应对常见的性能问题。对于更复杂的需求,如页面级别的状态保存,可以考虑使用第三方库如react-activation。记得始终要根据实际性能测量结果来决定是否真的需要使用这些优化措施,以避免过度优化带来的额外复杂性。

react如何避免全局渲染

在 React 应用中,避免全局渲染(即整个应用或组件树的重新渲染)对于提升性能非常重要。当一个组件的状态发生变化时,React 会默认重新渲染该组件及其所有子组件。然而,通过一些策略和最佳实践,你可以确保只有必要的部分被重新渲染。以下是一些常见的方法来优化并避免不必要的全局渲染:

1. 使用 React.memoPureComponent

  • React.memo:这是一个高阶组件,用于包裹函数组件,以防止不必要的重新渲染。它通过浅比较 props 来决定是否需要重新渲染。
  • PureComponent:对于类组件,可以继承自 React.PureComponent 而不是 React.ComponentPureComponent 会对 props 和 state 进行浅比较,从而避免不必要的渲染。
// 函数组件使用 React.memo
const MyComponent = React.memo(({ prop1, prop2 }) => {
  // 组件内容
});

// 类组件继承自 PureComponent
class MyComponent extends React.PureComponent {
  render() {
    // 组件内容
  }
}

2. 利用 useMemouseCallback

  • useMemo:用于缓存计算结果。如果依赖项没有变化,则返回上次计算的结果,而不是重新计算。
  • useCallback:用于缓存函数实例。如果依赖项没有变化,则返回相同的函数引用,这有助于避免因传递新的函数引用而导致父组件的重新渲染。
import { useMemo, useCallback } from 'react';

function MyComponent({ someProp }) {
  const memoizedValue = useMemo(() => computeExpensiveValue(someProp), [someProp]);

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return <ChildComponent value={memoizedValue} onClick={handleClick} />;
}

3. 拆分组件

将大组件拆分成更小、更专注的子组件。这样可以让每个组件只负责自己关心的数据,并且只在相关数据改变时才重新渲染。

4. 避免不必要的状态提升

尽量减少将状态提升到共同祖先组件的情况,因为这样做可能会导致更多组件的重新渲染。考虑使用 Context API 或者 Redux 等状态管理库来更好地控制状态流。

5. 使用 Context API

Context 可以让你在不通过 props 直接传递的情况下共享状态。这对于跨多个层级传递数据非常有用,同时也可以减少中间组件的重新渲染。

const MyContext = React.createContext();

function App() {
  const [value, setValue] = useState(0);

  return (
    <MyContext.Provider value={{ value, setValue }}>
      <ChildComponent />
    </MyContext.Provider>
  );
}

function ChildComponent() {
  const { value, setValue } = useContext(MyContext);

  return <button onClick={() => setValue(value + 1)}>Increment</button>;
}

6. 避免直接修改 props

确保不会直接修改从父组件传入的 props。直接修改会导致不可预测的行为,并可能引起不必要的重新渲染。

7. 使用 shouldComponentUpdateReact.memo 的第二个参数

对于复杂的组件,你可能需要实现自定义的比较逻辑。对于类组件,可以通过重写 shouldComponentUpdate 方法;对于函数组件,可以在 React.memo 中提供第二个参数作为自定义比较器。

// 类组件
class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 自定义比较逻辑
    return nextProps.someProp !== this.props.someProp;
  }

  render() {
    // 组件内容
  }
}

// 函数组件
const MyComponent = React.memo(
  ({ someProp }) => {
    // 组件内容
  },
  (prevProps, nextProps) => prevProps.someProp === nextProps.someProp
);

通过上述方法,你可以有效地控制 React 应用中的重新渲染范围,从而提高应用的整体性能。记住,过度优化也可能带来复杂性的增加,因此在实际开发过程中要权衡性能与代码可维护性之间的平衡。

useCallback和useMemo区别及应该场景

useCallbackuseMemo 都是 React Hooks,用于优化性能,但它们解决的问题和使用场景有所不同。下面详细解释这两个 Hooks 的区别以及各自的适用场景。

useCallback

  • 定义useCallback 用于缓存函数实例,避免在每次渲染时都创建新的函数。

  • 作用:主要用于优化子组件的渲染。当一个父组件传递给子组件的回调函数频繁变化时,即使这些函数的内容没有改变,也会导致子组件重新渲染。通过 useCallback 可以确保只有在依赖项发生变化时才更新函数引用,从而避免不必要的重新渲染。

  • 语法

    const memoizedCallback = useCallback(
      () => {
        doSomething(a, b);
      },
      [a, b], // 依赖数组
    );
    
  • 适用场景

    • 当你需要将一个回调函数作为 prop 传递给子组件,并且这个函数内部依赖的状态或 props 不经常变化时。
    • 当你希望避免因函数引用变化而导致子组件不必要的重新渲染时。

useMemo

  • 定义useMemo 用于缓存计算结果。它会在特定依赖项变化时执行提供的函数并返回结果,如果依赖项未变,则返回上次计算的结果。

  • 作用:主要用于优化计算密集型的操作。当你有一个复杂的计算逻辑,而这个逻辑的结果并不需要在每次渲染时都重新计算时,可以使用 useMemo 来缓存结果。

  • 语法

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    
  • 适用场景

    • 当你有昂贵的计算操作(例如大量的数据处理、复杂的算法等),并且这些操作的结果不需要在每次渲染时都重新计算时。
    • 当你需要保持某些值的引用不变,以便于比较或传递给其他组件时。

主要区别

  1. 用途不同

    • useCallback 主要用于缓存函数实例,减少不必要的函数创建,从而避免子组件的重新渲染。
    • useMemo 主要用于缓存计算结果,减少不必要的计算,从而提高性能。
  2. 返回值不同

    • useCallback 返回一个函数。
    • useMemo 返回一个计算结果。
  3. 使用场景不同

    • useCallback 适用于优化传递给子组件的回调函数。
    • useMemo 适用于优化计算密集型的操作或需要保持引用不变的值。

示例

使用 useCallback

import React, { useCallback } from 'react';

function ParentComponent({ value }) {
  const handleButtonClick = useCallback(() => {
    console.log('Button clicked with value:', value);
  }, [value]); // 只有当 value 改变时才会生成新的函数

  return <ChildComponent onClick={handleButtonClick} />;
}

function ChildComponent({ onClick }) {
  return <button onClick={onClick}>Click me</button>;
}

使用 useMemo

import React, { useMemo } from 'react';

function ExpensiveComponent({ a, b }) {
  const expensiveValue = useMemo(() => {
    // 假设这是一个非常耗时的计算
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += a * b;
    }
    return result;
  }, [a, b]); // 只有当 a 或 b 改变时才会重新计算

  return <div>Expensive Value: {expensiveValue}</div>;
}

通过合理使用 useCallbackuseMemo,你可以有效地优化 React 应用的性能,避免不必要的重新渲染和重复计算。

react 生命周期都有什么?

一、React生命周期概述

React组件的生命周期可以分为三个主要阶段:挂载阶段(Mounting)更新阶段(Updating)卸载阶段(Unmounting)。每个阶段都伴随着一系列的生命周期方法,这些方法为开发者提供了在组件生命周期中的关键时刻执行代码的机会。

二、挂载阶段(Mounting)

挂载阶段是指组件被创建后,插入到DOM树中的过程。此阶段依次调用的生命周期方法如下:

  1. constructor(props)
    • 类的构造方法,在组件被创建时调用。用于初始化state和绑定事件处理函数。注意,constructor中不能调用this.setState(),因为此时组件还未挂载到DOM上。
  2. static getDerivedStateFromProps(props, state)
    • React 16.3引入的静态方法,在组件创建时和每次更新前调用。用于根据props更新state。如果不希望更新state,则返回null。
  3. render()
    • 渲染方法,根据组件的props和state返回React元素。render方法必须是一个纯函数,不能修改组件的状态或执行副作用。
  4. componentDidMount()
    • 组件挂载到DOM后调用。是执行副作用(如数据获取、订阅或手动更改DOM)的理想位置。

三、更新阶段(Updating)

更新阶段发生在组件的props或state发生变化时,React会重新渲染组件。此阶段依次调用的生命周期方法如下:

  1. static getDerivedStateFromProps(props, state)
    • 同挂载阶段,用于在更新前根据props更新state。
  2. shouldComponentUpdate(nextProps, nextState)
    • 返回一个布尔值,决定组件是否应该更新。默认情况下返回true,但可以通过此方法优化性能,避免不必要的渲染。
  3. render()
    • 同挂载阶段,根据更新后的props和state重新渲染组件。
  4. getSnapshotBeforeUpdate(prevProps, prevState)
    • 在最近一次渲染输出(提交到DOM)之前调用,可以捕获一些信息(如滚动位置),这些信息将作为参数传递给componentDidUpdate
  5. componentDidUpdate(prevProps, prevState, snapshot)
    • 组件更新后被调用。可以在这里执行依赖于DOM的操作,如重新获取DOM尺寸或执行网络open in new window请求。

四、卸载阶段(Unmounting)

卸载阶段发生在组件从DOM中移除时。此阶段调用的生命周期方法如下:

  1. componentWillUnmount()
    • 在组件卸载及销毁之前调用。是执行清理操作(如取消网络请求、清除定时器)的理想位置。

五、React Hooks中的生命周期

从React 16.8开始,引入了Hooks API,使得在函数组件中也能使用类似类组件的生命周期功能。useEffect Hook是其中最常用的,它可以模拟componentDidMountcomponentDidUpdatecomponentWillUnmount等生命周期方法。

import React, { useState, useEffect } from 'react';function MyComponent() {  const [count, setCount] = useState(0);  useEffect(() => {    // 组件挂载或更新时执行    console.log('Component mounted or updated');    // 清理函数,组件卸载时执行    return () => {      console.log('Component will unmount');    };  }, [count]); // 依赖项数组,只有当依赖项变化时才会重新执行  return (    <div>      <p>You clicked {count} times</p>      <button onClick={() => setCount(count + 1)}>        Click me      </button>    </div>  );}

六、总结

React组件的生命周期是组件从创建到销毁的完整过程,了解并合理利用这些生命周期方法,

react 中hooks useEffect 和生命周期对应关系?

在 React 中,Hooks 提供了一种使用函数组件(Functional Components)编写具有状态逻辑和生命周期方法的能力。useEffect 是一个非常重要的 Hook,它允许你在函数组件中执行副作用操作,比如数据获取、订阅或手动更改 React 组件的 DOM。useEffect 的行为可以类比于多个类组件生命周期方法的组合。下面是 useEffect 与类组件生命周期方法之间的对应关系:

不带依赖项的 useEffect

useEffect 不带第二个参数(依赖数组)时,它的行为类似于以下类组件生命周期方法的组合:

  • componentDidMount:首次渲染后执行。
  • componentDidUpdate:每次更新后执行。
  • componentWillUnmount:卸载前清理副作用。
useEffect(() => {
  // 在这里执行副作用操作

  // 返回一个清理函数,这个函数会在组件卸载前或下次渲染前执行
  return () => {
    // 清理工作
  };
});

带空依赖数组的 useEffect

useEffect 的第二个参数是一个空数组 [] 时,它仅在组件挂载时执行一次,类似于 componentDidMountcomponentWillUnmount 的组合。这种情况下,useEffect 通常用于设置一次性副作用,如事件监听器或数据获取。

useEffect(() => {
  // 只在组件挂载时执行
  // 类似 componentDidMount

  return () => {
    // 在组件卸载前执行
    // 类似 componentWillUnmount
  };
}, []);  // 空数组表示只在挂载时运行

带有特定依赖项的 useEffect

useEffect 的第二个参数是一个非空数组时,只有当这些依赖项中的任何一个发生变化时,useEffect 才会重新执行。这相当于 componentDidMount 和带有条件的 componentDidUpdate 的结合。

useEffect(() => {
  // 当 count 或者 otherProp 发生变化时,此 useEffect 将重新执行
  // 类似 componentDidUpdate, 但仅在指定的 props 或 state 改变时触发

  return () => {
    // 清理工作
  };
}, [count, otherProp]);  // 指定依赖项

通过这种方式,useEffect 提供了对副作用管理的强大控制力,使得你可以根据实际需要灵活地定义何时以及如何执行副作用。记住,合理利用依赖数组是避免不必要的重复执行的关键。

react严格模式

React 的严格模式(Strict Mode)是一个用于突出显示应用程序中潜在问题的工具。它不会在生产构建中产生任何效果,但在开发环境中可以帮助你发现一些常见的编程错误或不推荐的做法。严格模式通过模拟某些生命周期方法的双重调用来帮助开发者发现这些问题。

如何启用严格模式

要启用严格模式,你需要将 <StrictMode> 组件包裹在你的应用顶层组件周围。通常你会在 index.jsApp.js 文件中这样做。

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StrictMode } from 'react';

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

严格模式做了什么

  1. 识别不安全的生命周期

    • 在旧版React类组件中,严格模式会警告使用了即将废弃的生命周期方法,如UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate
  2. 检测过时的引用

    • 如果你在卸载一个组件后仍然使用其状态或属性,严格模式会发出警告。
  3. 双重渲染以查找副作用

    • 严格模式会在开发模式下对函数组件进行两次调用,这有助于发现由于不必要的副作用而产生的bug,例如在useEffect中执行了应该只运行一次的操作却没有正确处理依赖数组。
  4. 检查遗留API的使用

    • 严格模式还会检查并警告关于使用过时或不推荐使用的React API。
  5. 确保可预测的状态更新

    • 严格模式有助于确保状态更新是同步的,并且可以防止由于异步更新导致的一些难以追踪的问题。

注意事项

  • 严格模式仅在开发环境生效,在生产环境中会被忽略。
  • 双重渲染可能会导致你的组件看起来像是被渲染了两次,但这只是在开发过程中帮助你发现潜在问题的一种手段。
  • 如果你的代码中存在与双重渲染相关的副作用,那么你应该修复这些副作用,而不是禁用严格模式。

启用严格模式是一个很好的实践,因为它能帮助你编写更加健壮和可靠的React应用。如果你的应用在启用严格模式后出现了警告,那么你应该根据这些警告来改进你的代码。

不关闭严格模式怎么让useEffect 执行一次

react 组件性能优化

React 组件的性能优化是一个重要的话题,尤其是在构建大型应用时。以下是一些常见的性能优化技巧和最佳实践:

1. 使用 React.memoPureComponent

  • React.memo:对于函数组件,可以使用 React.memo 高阶组件来避免不必要的重新渲染。它会浅比较 props,如果 props 没有变化,则不会重新渲染组件。
  • PureComponent:对于类组件,可以继承自 React.PureComponent。它类似于 React.memo,会对 props 和 state 进行浅比较。
// 函数组件
const MyComponent = React.memo((props) => {
  // 组件逻辑
});

// 类组件
class MyComponent extends React.PureComponent {
  // 组件逻辑
}

2. 使用 useMemouseCallback

  • useMemo:用于缓存计算结果。当依赖项没有变化时,返回之前缓存的结果,而不是重新计算。
  • useCallback:用于缓存函数定义。当依赖项没有变化时,返回之前缓存的函数,而不是创建新的函数实例。
import { useMemo, useCallback } from 'react';

function MyComponent({ a, b }) {
  const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

  const memoizedCallback = useCallback(() => {
    doSomething(a, b);
  }, [a, b]);

  // 组件逻辑
}

3. 代码分割 (Code Splitting)

  • 使用动态 import() 来实现代码分割,这样可以按需加载组件,减少初始加载时间。
import React, { Suspense, lazy } from 'react';

const OtherComponent = lazy(() => import('./OtherComponent'));

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

4. 使用 shouldComponentUpdateReact.memo 的自定义比较函数

  • 对于复杂的对象或数组,可以提供一个自定义的比较函数来决定是否需要重新渲染。
const areEqual = (prevProps, nextProps) => {
  // 自定义比较逻辑
};

const MyComponent = React.memo(MyComponent, areEqual);

5. 避免在 render 方法中执行昂贵的操作

  • 将复杂的计算、数据处理等移到生命周期方法(如 useEffect)或外部函数中,以确保它们只在必要时运行。

6. 使用 key 提高列表渲染效率

  • 当渲染列表时,为每个元素提供一个唯一的 key,这有助于 React 识别哪些元素改变了,从而提高更新效率。
const listItems = items.map((item) =>
  <li key={item.id}>{item.name}</li>
);

7. 虚拟化长列表

  • 对于非常长的列表,可以使用虚拟滚动技术,如 react-windowreact-virtualized,只渲染可见区域的内容,从而提高性能。

8. 使用 Profiler 进行性能分析

  • React 的 Profiler 组件可以帮助你分析组件的渲染性能,找出瓶颈所在。
<Profiler id="MyComponent" onRender={(id, phase, actualDuration, baseDuration, startTime, commitTime, interactions) => {
  // 分析性能数据
}}>
  <MyComponent />
</Profiler>

9. 优化 Context 使用

  • 避免过度使用 Context,因为每次 Context 值变化时,所有订阅该 Context 的组件都会重新渲染。可以考虑使用 useMemouseContextSelector(React 18+)来优化。

10. 使用 Webpack 等工具进行打包优化

  • 通过配置 Webpack 等打包工具,可以进一步优化打包后的代码,例如使用 Tree Shaking、压缩代码等。

11. 使用懒加载和预加载

  • 对于非关键路径上的资源,可以使用懒加载(lazy loading),而对于即将进入视口的资源,可以使用预加载(preloading)。

12. 服务器端渲染 (SSR) 和静态生成

  • 对于某些场景,服务器端渲染(SSR)或静态生成可以显著提高首屏加载速度和 SEO。

通过这些技术和方法,你可以显著提升 React 应用的性能,提供更好的用户体验。每种方法都有其适用场景,根据具体需求选择合适的技术是关键。

在React中如何避免不必要的render?

在 React 中避免不必要的渲染(re-render)是性能优化的一个重要方面。以下是一些常用的方法来减少不必要的渲染:

1. 使用 React.memoPureComponent

  • React.memo:对于函数组件,可以使用 React.memo 来包裹组件,这样当 props 没有变化时,组件不会重新渲染。
  • PureComponent:对于类组件,可以继承 React.PureComponent。它会对 props 和 state 进行浅比较,如果它们没有变化,则跳过渲染。
// 函数组件
const MyComponent = React.memo((props) => {
  // 组件逻辑
});

// 类组件
class MyComponent extends React.PureComponent {
  // 组件逻辑
}

2. 使用 useMemouseCallback

  • useMemo:用于缓存计算结果。当依赖项没有变化时,返回之前缓存的结果,而不是重新计算。
  • useCallback:用于缓存函数定义。当依赖项没有变化时,返回之前缓存的函数实例,而不是创建新的函数实例。
import { useMemo, useCallback } from 'react';

function MyComponent({ a, b }) {
  const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

  const memoizedCallback = useCallback(() => {
    doSomething(a, b);
  }, [a, b]);

  // 组件逻辑
}

3. 避免在 render 方法中执行昂贵的操作

  • 将复杂的计算、数据处理等移到生命周期方法(如 useEffect)或外部函数中,以确保它们只在必要时运行。

4. 使用 key 提高列表渲染效率

  • 当渲染列表时,为每个元素提供一个唯一的 key,这有助于 React 识别哪些元素改变了,从而提高更新效率。
const listItems = items.map((item) =>
  <li key={item.id}>{item.name}</li>
);

5. 优化 Context 使用

  • 避免过度使用 Context,因为每次 Context 值变化时,所有订阅该 Context 的组件都会重新渲染。可以考虑使用 useMemouseContextSelector(React 18+)来优化。
const value = useContext(MyContext);
const myValue = useMyContextSelector(value, (v) => v.myValue);

6. 使用自定义的 shouldComponentUpdateReact.memo 的比较函数

  • 对于复杂的对象或数组,可以提供一个自定义的比较函数来决定是否需要重新渲染。
const areEqual = (prevProps, nextProps) => {
  return prevProps.someProp === nextProps.someProp;
};

const MyComponent = React.memo(MyComponent, areEqual);

7. 使用 useReducer 管理复杂状态

  • 对于复杂的状态管理,使用 useReducer 可以更清晰地管理状态,并且可以通过选择性地更新部分状态来避免不必要的渲染。
const [state, dispatch] = useReducer(reducer, initialState);

// 在 reducer 中只更新必要的状态
function reducer(state, action) {
  switch (action.type) {
    case 'someAction':
      return { ...state, somePart: updatedValue };
    // 其他情况
  }
}

8. 优化事件处理器

  • 如果事件处理器依赖于某些 props 或 state,可以使用 useCallback 来避免每次渲染都创建新的函数实例。
const handleClick = useCallback(() => {
  // 事件处理逻辑
}, [dependency1, dependency2]);

9. 优化父组件

  • 父组件的重新渲染会导致子组件也重新渲染。确保父组件尽可能少地触发重新渲染,可以通过上述提到的 React.memouseMemo 来实现。

10. 使用 Profiler 进行性能分析

  • 使用 React 的 Profiler 组件来分析组件的渲染性能,找出瓶颈所在,并针对性地进行优化。
<Profiler id="MyComponent" onRender={(id, phase, actualDuration, baseDuration, startTime, commitTime, interactions) => {
  // 分析性能数据
}}>
  <MyComponent />
</Profiler>

通过这些方法,你可以有效地减少不必要的渲染,提升应用的性能。每种方法都有其适用场景,根据具体需求选择合适的技术是关键。

react 中 key 的作用

在 React 中,key 是一个特殊的属性,用于帮助 React 识别哪些元素发生了变化、被添加或被删除。key 的主要作用包括:

  1. 帮助 React 识别元素

    • 当你创建一个元素列表时,React 会使用 key 来跟踪每个元素的身份。key 应该是唯一的,并且在整个列表中保持稳定。
    • 如果没有提供 key,React 将默认使用索引作为 key,但这不是最佳实践,因为当列表顺序发生变化时,可能会导致性能问题和状态丢失。
  2. 优化渲染性能

    • 使用 key 可以帮助 React 更高效地重新渲染列表。当列表中的元素发生变化时,React 会根据 key 来决定如何最小化 DOM 操作。
    • 如果 key 保持不变,React 可以重用现有的 DOM 节点,而不是销毁并重新创建它们,从而提高性能。
  3. 维护组件状态

    • 在某些情况下,组件可能依赖于内部状态(例如,通过 useState 或类组件的 this.state)。如果列表中的元素位置发生变化,但没有使用 key,React 可能无法正确地维护这些状态。
    • 使用 key 可以确保即使元素的位置发生变化,其状态也会被正确地保留。

使用 key 的示例

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

return <ul>{listItems}</ul>;

在这个例子中,每个 <li> 元素都有一个基于 number 的唯一 key。这样,即使 numbers 数组发生变化,React 也可以有效地更新列表。

最佳实践

  • 使用稳定的标识符:最好使用来自数据的唯一标识符作为 key,例如数据库 ID。这可以确保 key 在多次渲染之间保持一致。
  • 避免使用索引:虽然使用数组索引作为 key 在某些简单场景下是可以接受的,但在列表项的顺序可能会变化的情况下,这会导致性能问题和潜在的状态丢失。
  • 不要使用随机生成的 key:每次渲染时生成新的随机 key 会使 React 认为所有元素都是新的,从而导致不必要的 DOM 操作。
  • 不要在循环外使用 keykey 仅在数组渲染的上下文中有意义,不应该在其他地方使用。

示例:使用不合适的 key 导致的问题

假设我们有一个简单的待办事项列表,每个事项都有一个唯一的 id 和一个完成状态:

const todos = [
  { id: 1, text: 'Learn React', completed: false },
  { id: 2, text: 'Build an app', completed: true }
];

const TodoItem = ({ todo }) => {
  const [completed, setCompleted] = useState(todo.completed);

  const handleToggle = () => {
    setCompleted(!completed);
  };

  return (
    <li onClick={handleToggle} style={{ textDecoration: completed ? 'line-through' : 'none' }}>
      {todo.text}
    </li>
  );
};

const TodoList = ({ todos }) => {
  return (
    <ul>
      {todos.map((todo, index) => (
        <TodoItem key={index} todo={todo} /> // 不推荐使用索引作为 key
      ))}
    </ul>
  );
};

如果 todos 列表的顺序发生变化(例如,用户对列表进行了排序),由于 key 是基于索引的,React 会认为所有的 TodoItem 都是新的,从而重新创建它们,导致之前的状态(如 completed)丢失。

正确的做法是使用 todo.id 作为 key

{todos.map((todo) => (
  <TodoItem key={todo.id} todo={todo} />
))}

这样,无论列表顺序如何变化,每个 TodoItem 的状态都会被正确地保留。

react 如何解决数据错误出现的白屏

在 React 应用中,数据错误(如网络请求失败、数据解析错误等)可能会导致白屏问题。为了解决这个问题,可以采取以下几种策略来增强应用的健壮性和用户体验:

1. 错误边界 (Error Boundaries)

  • 什么是错误边界:错误边界是一种 React 组件,它可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且还会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。
  • 如何使用错误边界:你可以创建一个类组件,并定义 static getDerivedStateFromErrorcomponentDidCatch 生命周期方法。
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

// 使用错误边界
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

2. 异常处理

  • 全局异常处理:可以在应用的顶层添加全局异常处理,以捕获未被错误边界捕获的异常。这可以通过监听 window.onerror 事件或使用第三方库(如 react-error-boundary)来实现。
import { ErrorBoundary } from 'react-error-boundary';

const MyFallbackComponent = ({ error, resetErrorBoundary }) => (
  <div role="alert">
    <p>Something went wrong:</p>
    <pre>{error.message}</pre>
    <button onClick={resetErrorBoundary}>Try again</button>
  </div>
);

function App() {
  return (
    <ErrorBoundary FallbackComponent={MyFallbackComponent}>
      <MyApp />
    </ErrorBoundary>
  );
}

3. 网络请求错误处理

  • 在数据获取时处理错误:确保在网络请求失败时有适当的错误处理逻辑,例如显示友好的错误消息或重试机制。
import React, { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => setData(data))
      .catch(error => setError(error));
  }, []);

  if (error) {
    return <div>Error: {error.message}</div>;
  }

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

  return <div>Data: {JSON.stringify(data)}</div>;
}

4. 前端缓存

  • 使用前端缓存:对于一些静态数据或不经常变化的数据,可以考虑使用前端缓存(如 localStorage 或 sessionStorage),以便在网络请求失败时提供一个备份数据源。

5. 优雅降级

  • 提供默认内容:在数据加载失败时,提供默认的内容或占位符,以保证用户界面的完整性。

6. 监控和日志

  • 错误监控:使用工具如 Sentry、LogRocket 等来监控和记录生产环境中的错误,这样可以及时发现并修复问题。
  • 日志记录:在开发过程中,记录详细的日志可以帮助快速定位问题。

通过上述方法,你可以有效地处理数据错误,避免出现白屏问题,同时提供更好的用户体验。

用hooks实现一个倒计时

基本倒计时组件

这个组件将从一个指定的时间(例如 10 秒)开始倒计时,并在每秒更新显示剩余时间。当倒计时结束时,它会显示一条消息。

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

function Countdown({ seconds }) {
  // 初始化状态,设置为传入的秒数
  const [count, setCount] = useState(seconds);

  // 使用 useEffect 来启动定时器
  useEffect(() => {
    // 如果 count 大于 0,则设置一个定时器
    if (count > 0) {
      const timerId = setTimeout(() => {
        setCount(count - 1);
      }, 1000); // 每 1000 毫秒(1 秒)更新一次

      // 清理函数:在组件卸载或依赖项变化时清除定时器
      return () => clearTimeout(timerId);
    }
  }, [count]); // 依赖 count,每当 count 变化时重新设置定时器

  // 当倒计时结束时显示的消息
  if (count === 0) {
    return <div>Time's up!</div>;
  }

  // 显示当前的倒计时
  return (
    <div>
      <h1>Countdown: {count} seconds</h1>
    </div>
  );
}

export default Countdown;

使用组件

你可以像下面这样在另一个组件中使用 Countdown 组件:

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

function App() {
  return (
    <div className="App">
      <h1>React Countdown Example</h1>
      <Countdown seconds={10} />
    </div>
  );
}

export default App;

解释

  • 状态管理:我们使用 useState 来初始化倒计时的状态 count
  • 副作用处理useEffect 用于设置定时器。每次 count 状态发生变化时,都会重新设置一个新的定时器。
  • 清理定时器:通过返回一个清理函数,我们在组件卸载或 count 发生变化时清除之前的定时器,以避免内存泄漏。
  • 条件渲染:当 count 达到 0 时,显示 "Time's up!" 消息;否则,显示当前的倒计时。

进阶功能

如果你需要更复杂的功能,比如暂停、继续倒计时,或者提供自定义的格式化输出,可以进一步扩展这个组件。例如,添加额外的状态来控制暂停/继续,以及更多的逻辑来处理这些状态的变化。

这是一个基本的倒计时组件实现,可以根据实际需求进行调整和扩展。希望这对你有所帮助!

React 组件的 state 和 props 有什么区别

在 React 中,stateprops 是两个核心概念,它们在组件的行为和数据流中扮演着不同的角色。理解它们的区别对于编写高效且可维护的 React 应用非常重要。

State (状态)

  1. 定义

    • state 是组件内部管理的数据,它是私有的,并且完全受控于该组件。
    • state 可以通过 this.state(类组件)或 useState Hook(函数组件)来访问和更新。
  2. 用途

    • 用于存储组件的动态数据,这些数据可能在组件的生命周期中发生变化。
    • 例如,表单输入、计数器、开关状态等。
  3. 更新方式

    • 在类组件中,使用 this.setState 方法来更新 state
    • 在函数组件中,使用 useState Hook 返回的 setState 函数来更新 state
  4. 影响

    • 更新 state 会触发组件的重新渲染。
  5. 示例

    // 类组件
    class Counter extends React.Component {
      constructor(props) {
        super(props);
        this.state = { count: 0 };
      }
    
      increment = () => {
        this.setState({ count: this.state.count + 1 });
      };
    
      render() {
        return (
          <div>
            <p>Count: {this.state.count}</p>
            <button onClick={this.increment}>Increment</button>
          </div>
        );
      }
    }
    
    // 函数组件
    import React, { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0);
    
      const increment = () => {
        setCount(count + 1);
      };
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={increment}>Increment</button>
        </div>
      );
    }
    

Props (属性)

  1. 定义

    • props 是从父组件传递给子组件的数据,它是只读的,子组件不能修改 props
    • props 可以通过 this.props(类组件)或直接作为函数参数(函数组件)来访问。
  2. 用途

    • 用于从父组件向子组件传递数据和回调函数。
    • 例如,传递文本内容、样式、事件处理器等。
  3. 更新方式

    • props 的更新由父组件控制,子组件无法直接修改 props
    • 父组件通过重新渲染并传递新的 props 来更新子组件的状态。
  4. 影响

    • props 发生变化时,子组件会重新渲染。
  5. 示例

    // 父组件
    import React, { useState } from 'react';
    import ChildComponent from './ChildComponent';
    
    function ParentComponent() {
      const [message, setMessage] = useState('Hello, World!');
    
      return (
        <div>
          <input
            type="text"
            value={message}
            onChange={(e) => setMessage(e.target.value)}
          />
          <ChildComponent message={message} />
        </div>
      );
    }
    
    // 子组件
    function ChildComponent(props) {
      return <p>{props.message}</p>;
    }
    

主要区别

  • 所有权

    • state 是组件私有的,由组件自身管理和更新。
    • props 是从父组件传递给子组件的,子组件只能读取而不能修改。
  • 更新机制

    • state 的更新会导致组件重新渲染。
    • props 的更新也导致组件重新渲染,但 props 的更新是由父组件控制的。
  • 数据流

    • state 通常用于管理组件内部的状态。
    • props 用于在组件之间传递数据和行为,实现父子组件之间的通信。
  • 可变性

    • state 是可变的,可以通过 setStateuseStatesetState 函数进行更新。
    • props 是不可变的,子组件不能修改 props

理解 stateprops 的区别有助于你更好地设计组件结构和数据流,从而构建出更加清晰和易于维护的 React 应用。

React 中的 props 为什么是只读的?

this.props 是组件之间沟通的一个接口,原则上来讲,它只能从父组件流向子组件。

React 具有浓重的函数式编程的思想。

提到函数式编程就要提一个概念:纯函数。它有几个特点:

给定相同的输入,总是返回相同的输出。

过程没有副作用。

不依赖外部状态。

this.props 就是汲取了纯函数的思想。props 的不可以变性就保证的相同的输入,页面显示的内容是一样的,并且不会产生副作用。

Last Updated:
Contributors: 乙浒