Skip to content

React 面试题

1. react中的hook的什么?为什么引入hook?useCallback的作用是什么?

Details

哦,React 中的 Hook 是一种函数,可以让你在函数组件中使用 React 特性。引入 Hook 的初衷是为了让函数组件具备类组件的功能,使得函数组件能够更方便地管理状态、生命周期等。使用 Hook 可以让代码更简洁、可读性更好。

useCallback 是 React 中的一个 Hook,它用于缓存一个回调函数,避免在每次组件渲染时都创建新的回调函数实例。这对于性能优化很有帮助,特别是在传递回调函数给子组件时,可以避免不必要的重新渲染。

举个例子,如果你有一个回调函数 handleClick,你可以使用 useCallback 来缓存这个函数:

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

在这个例子中,handleClick 函数会在组件初始化时被创建,并且只有依赖项发生变化时才会重新创建。这样可以提高性能,避免不必要的函数重复创建。

总的来说,useCallback 的作用就是缓存回调函数,优化性能。如果你的组件有一些依赖于回调函数的逻辑,可以考虑使用 useCallback 来提高效率。

2. react中有哪些常用的hook,举例并说明其作用

Details

在 React 中,Hooks 是一种可以让你在函数组件中使用状态和其他 React 特性的方法。以下是一些常用的 Hook 及其简单示例和说明:

1. 内置hook

text
1. useState:用于在函数组件中添加状态。
2. useEffect:用于处理副作用,比如数据获取或订阅。
3. useContext:用于在组件树中共享状态。
4. useReducer:用于管理复杂的状态逻辑。
5. useMemo:用于优化性能,避免不必要的计算。
6. useCallback:用于优化性能,记忆一个函数实例。
7. useRef:用于访问 DOM 元素或存储可变的值。
8. useLayoutEffect:在 DOM 更新后同步执行副作用。
9. useImperativeHandle:用于在使用 forwardRef 时自定义组件实例的暴露内容。
10. useDebugValue:用于在 React DevTools 中显示自定义 Hook 的标签。
  1. useState:用于在函数组件中添加状态。

    javascript
    import React, { useState } from 'react';
    
    function Counter() {
        // 声明一个新的状态变量 "count",初始值为 0
        const [count, setCount] = useState(0);
    
        return (
            <div>
                <p>当前计数: {count}</p>
                <button onClick={() => setCount(count + 1)}>
                    增加计数
                </button>
            </div>
        );
    }

    解释useState 是一个 Hook,用于在组件中引入状态。它返回一个数组,第一个元素是当前状态的值,第二个元素是更新这个状态的函数。

  2. useEffect:用于处理副作用,比如数据获取、订阅或手动修改 DOM。

    javascript
    import React, { useState, useEffect } from 'react';
    
    function DataFetcher() {
        const [data, setData] = useState(null);
    
        useEffect(() => {
            // 模拟数据获取
            fetch('https://api.example.com/data')
                .then(response => response.json())
                .then(data => setData(data));
        }, []); // 空数组意味着这个 effect 只在组件首次渲染时运行
    
        return (
            <div>
                {data ? <p>数据: {JSON.stringify(data)}</p> : <p>加载中...</p>}
            </div>
        );
    }

    解释useEffect 用于处理副作用,比如进行网络请求。它的第二个参数是一个数组,表示依赖项,只有当依赖项发生变化时,effect 才会重新执行。

  3. useContext:用于在组件树中共享状态,而不必通过 props 一层一层传递。

    javascript
    import React, { useContext, createContext } from 'react';
    
    const ThemeContext = createContext('light'); // 创建一个上下文,默认值为 'light'
    
    function ThemedComponent() {
        const theme = useContext(ThemeContext); // 使用 useContext 获取上下文值
    
        return <div style={{ background: theme === 'dark' ? '#333' : '#FFF' }}>主题: {theme}</div>;
    }
    
    function App() {
        return (
            <ThemeContext.Provider value="dark">
                <ThemedComponent />
            </ThemeContext.Provider>
        );
    }

    解释useContext 允许你在组件中访问上下文的值,避免了通过 props 一层层传递。上下文是 React 提供的一个功能,用于在组件树中共享数据。

  4. useReducer:用于管理复杂的状态逻辑,类似于 Redux 的 reducer。

    javascript
    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>
                <p>当前计数: {state.count}</p>
                <button onClick={() => dispatch({ type: 'increment' })}>增加计数</button>
                <button onClick={() => dispatch({ type: 'decrement' })}>减少计数</button>
            </div>
        );
    }

    解释useReducer 是一个 Hook,用于管理复杂的状态逻辑。它接受一个 reducer 函数和初始状态,返回当前状态和一个 dispatch 函数,用于发送 action 来更新状态。

  5. useMemo:用于优化性能,避免在每次渲染时都重新计算值。

    javascript
    import React, { useState, useMemo } from 'react';
    
    function ExpensiveComputationComponent({ number }) {
        const [count, setCount] = useState(0);
    
        // 仅在 number 改变时重新计算
        const computedValue = useMemo(() => {
            console.log('计算中...');
            return number * 2; // 假设这是一个耗时的计算
        }, [number]);
    
        return (
            <div>
                <p>计算值: {computedValue}</p>
                <p>当前计数: {count}</p>
                <button onClick={() => setCount(count + 1)}>增加计数</button>
            </div>
        );
    }

    解释useMemo 用于记忆计算结果,只有在依赖项变化时才会重新计算,从而提高性能,避免不必要的计算。

  6. useCallback:用于优化性能,记忆一个函数实例,只有在依赖项变化时才会更新。

    javascript
    import React, { useState, useCallback } from 'react';
    
    function Counter() {
        const [count, setCount] = useState(0);
    
        // 记忆这个函数,只有当 count 改变时才会更新
        const increment = useCallback(() => {
            setCount(c => c + 1);
        }, []);
    
        return (
            <div>
                <p>当前计数: {count}</p>
                <button onClick={increment}>增加计数</button>
            </div>
        );
    }

    解释useCallback 用于记忆一个函数,避免在每次渲染时都创建新的函数实例,特别在将函数传递给子组件时,可以避免不必要的重新渲染。

  7. useRef:用于访问 DOM 元素或存储可变的值,而不触发重新渲染。

    javascript
    import React, { useRef } from 'react';
    
    function FocusInput() {
        const inputRef = useRef(null);
    
        const focusInput = () => {
            inputRef.current.focus(); // 访问 DOM 元素并聚焦
        };
    
        return (
            <div>
                <input ref={inputRef} type="text" />
                <button onClick={focusInput}>聚焦输入框</button>
            </div>
        );
    }

    解释useRef 返回一个可变的 ref 对象,可以用来直接访问 DOM 元素,或存储不需要触发重新渲染的值。

  8. useLayoutEffect:与 useEffect 类似,但它在 DOM 更新后同步执行。通常用于需要读取布局并同步触发重绘的情况。

    javascript
    import React, { useLayoutEffect, useRef } from 'react';
    
    function LayoutEffectExample() {
        const divRef = useRef(null);
    
        useLayoutEffect(() => {
            console.log('布局效果:', divRef.current.getBoundingClientRect());
        });
    
        return <div ref={divRef}>这是一个例子</div>;
    }

    解释useLayoutEffect 在浏览器绘制之前执行,这意味着它可以读取布局并同步触发重绘,适合某些需要立即更新 DOM 的场景。

  9. useImperativeHandle:用于在使用 forwardRef 时自定义组件实例的暴露内容。

    javascript
    import React, { useImperativeHandle, useRef, forwardRef } from 'react';
    
    const CustomInput = forwardRef((props, ref) => {
        const inputRef = useRef();
    
        useImperativeHandle(ref, () => ({
            focus: () => {
                inputRef.current.focus(); // 自定义暴露的方法
            }
        }));
    
        return <input ref={inputRef} type="text" />;
    });
    
    function ParentComponent() {
        const inputRef = useRef();
    
        const focusInput = () => {
            inputRef.current.focus(); // 调用自定义暴露的方法
        };
    
        return (
            <div>
                <CustomInput ref={inputRef} />
                <button onClick={focusInput}>聚焦输入框</button>
            </div>
        );
    }

    解释useImperativeHandle 允许你在函数组件中控制暴露给父组件的实例值,通常与 forwardRef 一起使用。

  10. useDebugValue:用于在 React DevTools 中显示自定义 Hook 的标签。

    javascript
    import React, { useState, useDebugValue } from 'react';
    
    function useFriendStatus(friendID) {
        const [isOnline, setIsOnline] = useState(null);
    
        // 根据 isOnline 的值设置调试标签
        useDebugValue(isOnline ? '在线' : '离线');
    
        // 这里可以添加其他逻辑来管理好友状态
        return isOnline;
    }
    
    function FriendStatus({ friendID }) {
        const isOnline = useFriendStatus(friendID);
    
        return <div>{isOnline ? '在线' : '离线'}</div>;
    }

    解释useDebugValue 主要用于在 React DevTools 中提供调试信息,帮助开发者更好地理解和调试自定义 Hook。

2. 自定义 Hook

除了 React 提供的内建 Hook,你还可以根据自己的需求创建自定义 Hook。这些 Hook 组合了多个内建 Hook,封装了特定的逻辑。

例如,一个自定义 Hook 用于处理窗口大小变化:

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

function useWindowSize() {
    const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });

    useEffect(() => {
        const handleResize = () => {
            setSize({ width: window.innerWidth, height: window.innerHeight });
        };

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

    return size;
}

function WindowSizeComponent() {
    const size = useWindowSize();

    return (
        <div>
            <p>窗口宽度: {size.width}px</p>
            <p>窗口高度: {size.height}px</p>
        </div>
    );
}

解释:上述 useWindowSize Hook 监听窗口大小变化,并提供当前窗口的宽度和高度。自定义 Hook 使得逻辑复用变得简单,可在多个组件中共享。

以上是一些额外的 Hook 和自定义 Hook 的示例。通过这些 Hook,你可以更灵活地管理组件的状态和行为,提高代码的可复用性和可读性。如果你有其他问题或想要更深入的了解某个 Hook,欢迎继续提问!

3. React 的生命周期

Details

React 组件的生命周期是指组件从创建到销毁的整个过程,不同版本的 React 生命周期有所不同。

类组件生命周期

React 16.3 之前

  1. 挂载阶段

    • constructor:组件构造函数,初始化状态和绑定方法
    • componentWillMount:组件即将挂载(已废弃)
    • render:渲染组件
    • componentDidMount:组件挂载完成,适合进行数据获取、订阅等操作
  2. 更新阶段

    • componentWillReceiveProps:接收新 props(已废弃)
    • shouldComponentUpdate:判断是否需要更新组件
    • componentWillUpdate:组件即将更新(已废弃)
    • render:渲染组件
    • componentDidUpdate:组件更新完成,适合处理 DOM 操作和网络请求
  3. 卸载阶段

    • componentWillUnmount:组件即将卸载,适合清理副作用,如取消订阅、清除定时器等

React 16.3 及之后

  1. 挂载阶段

    • constructor:组件构造函数
    • static getDerivedStateFromProps:从 props 派生状态
    • render:渲染组件
    • componentDidMount:组件挂载完成
  2. 更新阶段

    • static getDerivedStateFromProps:从 props 派生状态
    • shouldComponentUpdate:判断是否需要更新组件
    • render:渲染组件
    • getSnapshotBeforeUpdate:在 DOM 更新前获取快照
    • componentDidUpdate:组件更新完成
  3. 卸载阶段

    • componentWillUnmount:组件即将卸载
  4. 错误处理

    • componentDidCatch:捕获子组件错误

函数组件生命周期(使用 Hooks)

函数组件通过 Hooks 来模拟生命周期:

  1. 挂载阶段

    • useState 初始化状态
    • useEffect 带空依赖数组,相当于 componentDidMount
  2. 更新阶段

    • useState 更新状态
    • useEffect 带依赖项,相当于 componentDidUpdate
    • useMemouseCallback 优化性能
  3. 卸载阶段

    • useEffect 的返回函数,相当于 componentWillUnmount

示例

类组件

javascript
class LifecycleExample extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    console.log('constructor');
  }

  componentDidMount() {
    console.log('componentDidMount');
    this.timer = setInterval(() => {
      this.setState(prevState => ({ count: prevState.count + 1 }));
    }, 1000);
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('componentDidUpdate');
  }

  componentWillUnmount() {
    console.log('componentWillUnmount');
    clearInterval(this.timer);
  }

  render() {
    console.log('render');
    return <div>Count: {this.state.count}</div>;
  }
}

函数组件

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

  useEffect(() => {
    console.log('Component mounted');
    const timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
    return () => {
      console.log('Component unmounted');
      clearInterval(timer);
    };
  }, []);

  useEffect(() => {
    console.log('Component updated');
  }, [count]);

  console.log('Render');
  return <div>Count: {count}</div>;
}

总结

React 的生命周期管理着组件的整个生命周期,从创建到销毁。类组件有明确的生命周期方法,而函数组件通过 Hooks 来模拟这些生命周期。了解生命周期对于优化组件性能、处理副作用和管理资源非常重要。

4. React 中的虚拟 DOM 和 diff 算法

Details

虚拟 DOM 是 React 中的一个核心概念,它是对真实 DOM 的轻量级抽象,用于提高渲染性能。

虚拟 DOM 的工作原理

  1. 创建虚拟 DOM:当组件状态变化时,React 会创建一个新的虚拟 DOM 树。
  2. diff 算法:React 会使用 diff 算法比较新旧虚拟 DOM 树的差异。
  3. 生成补丁:根据差异生成最小的 DOM 操作补丁。
  4. 应用补丁:将补丁应用到真实 DOM 上,更新界面。

diff 算法的优化策略

  1. 同层比较:React 只比较同一层级的节点,不跨层级比较,减少了比较的复杂度。
  2. key 属性:使用 key 属性来唯一标识列表中的元素,帮助 React 快速识别元素的变化。
  3. 类型比较:如果节点类型不同,直接替换整个子树,不进行深入比较。
  4. 属性比较:对于相同类型的节点,比较它们的属性,只更新变化的属性。

示例

javascript
// 虚拟 DOM 结构
const vNode = {
  type: 'div',
  props: {
    className: 'container',
    children: [
      {
        type: 'h1',
        props: {
          children: 'Hello World'
        }
      },
      {
        type: 'p',
        props: {
          children: 'This is a paragraph'
        }
      }
    ]
  }
};

虚拟 DOM 的优势

  1. 性能优化:通过批量更新和最小化 DOM 操作,提高渲染性能。
  2. 跨平台:虚拟 DOM 可以渲染到不同的平台,如浏览器、服务器、移动端等。
  3. 简化开发:开发者可以专注于状态管理,而不必担心 DOM 操作的细节。

总结

虚拟 DOM 和 diff 算法是 React 性能优化的关键,它们通过减少真实 DOM 的操作次数,提高了渲染效率。理解虚拟 DOM 的工作原理和 diff 算法的优化策略,对于编写高性能的 React 应用非常重要。

5. React 中的状态管理

Details

React 中的状态管理是指如何管理和共享组件之间的状态。常见的状态管理方案包括:

1. 组件内部状态(useState)

适用场景:组件内部的简单状态管理。

javascript
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

2. Context API

适用场景:跨组件的状态共享,特别是当状态需要在多个组件中使用时。

javascript
// 创建上下文
const ThemeContext = createContext('light');

// 提供者组件
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 消费者组件
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button
      style={{ background: theme === 'dark' ? '#333' : '#fff', color: theme === 'dark' ? '#fff' : '#333' }}
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
    >
      Toggle Theme
    </button>
  );
}

3. Redux

适用场景:大型应用的复杂状态管理,特别是当状态需要在多个组件中共享且有复杂的状态逻辑时。

javascript
// 定义 reducer
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

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

// 连接组件
function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
    </div>
  );
}

4. Zustand

适用场景:轻量级状态管理,比 Redux 更简单易用。

javascript
// 创建 store
import create from 'zustand';

const useStore = create(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 }))
}));

// 使用 store
function Counter() {
  const { count, increment, decrement } = useStore();
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

5. Recoil

适用场景:Facebook 开发的状态管理库,适合处理复杂的状态依赖关系。

javascript
// 定义 atom
const countState = atom({
  key: 'countState',
  default: 0
});

// 使用 atom
function Counter() {
  const [count, setCount] = useRecoilState(countState);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

总结

React 提供了多种状态管理方案,从简单的组件内部状态到复杂的全局状态管理。选择合适的状态管理方案取决于应用的规模和复杂度。对于小型应用,使用 useStateContext API 就足够了;对于大型应用,可以考虑使用 Redux、Zustand 或 Recoil 等状态管理库。

6. React 中的性能优化策略

Details

React 性能优化是开发 React 应用时的重要考虑因素,以下是一些常见的性能优化策略:

1. 使用 React.memo

React.memo 是一个高阶组件,用于缓存组件的渲染结果,避免不必要的重新渲染。

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

2. 使用 useMemo

useMemo 用于缓存计算结果,避免在每次渲染时都重新计算。

javascript
const expensiveValue = useMemo(() => {
  // 复杂计算
  return calculateExpensiveValue(a, b);
}, [a, b]);

3. 使用 useCallback

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

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

4. 合理使用 key 属性

在列表渲染时,使用唯一的 key 属性帮助 React 识别元素的变化,提高渲染性能。

javascript
const items = [1, 2, 3].map(item => (
  <div key={item}>{item}</div>
));

5. 懒加载组件

使用 React.lazySuspense 实现组件的懒加载,减少初始加载时间。

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

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

6. 避免不必要的渲染

使用 shouldComponentUpdate(类组件)或 React.memo(函数组件)避免不必要的重新渲染。

javascript
// 类组件
class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 只有当 props 或 state 发生变化时才重新渲染
    return nextProps.name !== this.props.name || nextState.count !== this.state.count;
  }
  render() {
    return <div>{this.props.name}</div>;
  }
}

// 函数组件
const MemoizedComponent = React.memo(function MyComponent(props) {
  return <div>{props.name}</div>;
});

7. 使用虚拟列表

对于长列表,使用虚拟列表只渲染可视区域内的元素,提高渲染性能。

javascript
import { FixedSizeList as List } from 'react-window';

function VirtualList({ items }) {
  return (
    <List
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>{items[index]}</div>
      )}
    </List>
  );
}

8. 优化状态管理

使用适当的状态管理方案,避免状态的过度更新和不必要的渲染。

9. 减少 props 的传递

使用 Context API 或状态管理库减少 props 的层层传递,提高代码的可读性和性能。

10. 优化网络请求

使用缓存、防抖、节流等技术优化网络请求,减少不必要的 API 调用。

总结

React 性能优化是一个持续的过程,需要根据应用的具体情况选择合适的优化策略。通过合理使用 React 提供的优化工具和技术,可以显著提高应用的性能和用户体验。

7. React 中的高阶组件(HOC)和渲染属性(Render Props)

Details

高阶组件(HOC)和渲染属性(Render Props)是 React 中用于代码复用的两种模式。

高阶组件(HOC)

定义:高阶组件是一个函数,接收一个组件作为参数,返回一个新的组件。

使用场景:代码复用、逻辑抽象、横切关注点(如认证、日志、数据获取等)。

示例

javascript
// 定义 HOC
function withAuthentication(Component) {
  return function AuthenticatedComponent(props) {
    const [isAuthenticated, setIsAuthenticated] = useState(false);

    useEffect(() => {
      // 模拟认证检查
      const checkAuth = async () => {
        const result = await checkUserAuthentication();
        setIsAuthenticated(result);
      };
      checkAuth();
    }, []);

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

    return <Component {...props} />;
  };
}

// 使用 HOC
const ProtectedComponent = withAuthentication(function MyComponent(props) {
  return <div>Protected content</div>;
});

渲染属性(Render Props)

定义:渲染属性是一个组件的 prop,其值是一个函数,该函数返回 React 元素。

使用场景:代码复用、逻辑抽象、组件间通信。

示例

javascript
// 定义 Render Props 组件
class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.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.render(this.state)}
      </div>
    );
  }
}

// 使用 Render Props
function App() {
  return (
    <MouseTracker>
      {({ x, y }) => (
        <div>
          <p>Mouse position: {x}, {y}</p>
        </div>
      )}
    </MouseTracker>
  );
}

对比

特性高阶组件(HOC)渲染属性(Render Props)
语法函数接收组件,返回新组件组件接收函数作为 prop
复用方式通过包装组件实现通过传递函数实现
命名冲突可能发生 props 命名冲突避免了 props 命名冲突
灵活性较低较高
可读性可能较难理解通常更易理解

总结

高阶组件和渲染属性都是 React 中用于代码复用的重要模式。高阶组件适合于横切关注点的抽象,而渲染属性则提供了更灵活的组件间通信方式。在实际开发中,应根据具体场景选择合适的模式。

8. React 中的事件处理

Details

React 中的事件处理与原生 DOM 事件处理有所不同,它使用了合成事件(SyntheticEvent)系统。

合成事件

定义:合成事件是 React 对原生 DOM 事件的封装,提供了跨浏览器的一致接口。

特点

  • 跨浏览器兼容性:在不同浏览器中表现一致
  • 事件委托:所有事件都委托到 document 上,提高性能
  • 自动绑定:在类组件中,事件处理函数会自动绑定到组件实例

事件处理方法

1. 类组件中的事件处理

方法一:在构造函数中绑定

javascript
class Button extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log('Button clicked');
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

方法二:使用箭头函数

javascript
class Button extends React.Component {
  handleClick = () => {
    console.log('Button clicked');
  };

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

方法三:在渲染时使用箭头函数

javascript
class Button extends React.Component {
  handleClick() {
    console.log('Button clicked');
  }

  render() {
    return <button onClick={() => this.handleClick()}>Click me</button>;
  }
}

2. 函数组件中的事件处理

javascript
function Button() {
  const handleClick = () => {
    console.log('Button clicked');
  };

  return <button onClick={handleClick}>Click me</button>;
}

事件对象

React 中的事件对象是合成事件,它具有与原生 DOM 事件相同的属性和方法,但在事件处理函数执行完毕后会被销毁。

javascript
function handleClick(event) {
  event.preventDefault(); // 阻止默认行为
  event.stopPropagation(); // 阻止事件冒泡
  console.log(event.target); // 获取事件目标
}

传递参数

在类组件中

javascript
class List extends React.Component {
  handleClick(id) {
    console.log('Item clicked:', id);
  }

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

在函数组件中

javascript
function List({ items }) {
  const handleClick = (id) => {
    console.log('Item clicked:', id);
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

性能优化

在处理大量元素的事件时,应使用事件委托或 useCallback 来优化性能。

javascript
function List({ items }) {
  const handleClick = useCallback((id) => {
    console.log('Item clicked:', id);
  }, []);

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

总结

React 中的事件处理使用合成事件系统,提供了跨浏览器的一致接口。在类组件和函数组件中,事件处理的实现方式有所不同,但核心原理是相同的。合理使用事件处理可以提高应用的交互性和用户体验。

9. React 中的表单处理

Details

React 中的表单处理有两种主要方式:受控组件和非受控组件。

受控组件

定义:受控组件是指表单元素的值由 React 状态控制的组件。

特点

  • 表单元素的值与 React 状态同步
  • 可以实时验证用户输入
  • 可以控制表单元素的行为

示例

javascript
function ControlledForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

非受控组件

定义:非受控组件是指表单元素的值由 DOM 自身管理的组件。

特点

  • 表单元素的值由 DOM 管理
  • 可以使用 ref 来访问表单元素的值
  • 实现简单,适合简单的表单

示例

javascript
function UncontrolledForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = {
      name: nameRef.current.value,
      email: emailRef.current.value
    };
    console.log('Form submitted:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input type="text" ref={nameRef} />
      </div>
      <div>
        <label>Email:</label>
        <input type="email" ref={emailRef} />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

表单验证

实时验证

javascript
function FormWithValidation() {
  const [formData, setFormData] = useState({
    name: '',
    email: ''
  });
  const [errors, setErrors] = useState({});

  const validate = (name, value) => {
    const newErrors = { ...errors };
    if (name === 'name' && value.trim() === '') {
      newErrors.name = 'Name is required';
    } else {
      delete newErrors.name;
    }
    if (name === 'email' && !value.includes('@')) {
      newErrors.email = 'Invalid email';
    } else {
      delete newErrors.email;
    }
    setErrors(newErrors);
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
    validate(name, value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // 验证所有字段
    Object.keys(formData).forEach(name => validate(name, formData[name]));
    if (Object.keys(errors).length === 0) {
      console.log('Form submitted:', formData);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
        {errors.name && <p style={{ color: 'red' }}>{errors.name}</p>}
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
        {errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

表单库

对于复杂的表单,可以使用第三方表单库,如 Formik、React Hook Form 等。

Formik 示例

javascript
import { Formik, Form, Field, ErrorMessage } from 'formik';

function FormikForm() {
  return (
    <Formik
      initialValues={{ name: '', email: '' }}
      validate={values => {
        const errors = {};
        if (!values.name) {
          errors.name = 'Name is required';
        }
        if (!values.email) {
          errors.email = 'Email is required';
        } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
          errors.email = 'Invalid email address';
        }
        return errors;
      }}
      onSubmit={(values) => {
        console.log('Form submitted:', values);
      }}
    >
      {({ isSubmitting }) => (
        <Form>
          <div>
            <label>Name:</label>
            <Field type="text" name="name" />
            <ErrorMessage name="name" component="div" style={{ color: 'red' }} />
          </div>
          <div>
            <label>Email:</label>
            <Field type="email" name="email" />
            <ErrorMessage name="email" component="div" style={{ color: 'red' }} />
          </div>
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </Form>
      )}
    </Formik>
  );
}

总结

React 中的表单处理有受控组件和非受控组件两种方式。受控组件适合需要实时验证和控制的场景,而非受控组件适合简单的表单。对于复杂的表单,可以使用第三方表单库来简化开发。

10. React 中的路由(React Router)

Details

React Router 是 React 官方推荐的路由库,用于实现单页应用的路由功能。

基本使用

安装

bash
npm install react-router-dom

基本路由

javascript
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> |
        <Link to="/about">About</Link> |
        <Link to="/contact">Contact</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Router>
  );
}

function Home() {
  return <h1>Home</h1>;
}

function About() {
  return <h1>About</h1>;
}

function Contact() {
  return <h1>Contact</h1>;
}

动态路由

带参数的路由

javascript
import { BrowserRouter as Router, Routes, Route, Link, useParams } from 'react-router-dom';

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> |
        <Link to="/users/1">User 1</Link> |
        <Link to="/users/2">User 2</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/users/:id" element={<User />} />
      </Routes>
    </Router>
  );
}

function User() {
  const { id } = useParams();
  return <h1>User {id}</h1>;
}

嵌套路由

子路由

javascript
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> |
        <Link to="/dashboard">Dashboard</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />}>
          <Route index element={<DashboardHome />} />
          <Route path="settings" element={<DashboardSettings />} />
          <Route path="profile" element={<DashboardProfile />} />
        </Route>
      </Routes>
    </Router>
  );
}

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <nav>
        <Link to="/dashboard">Home</Link> |
        <Link to="/dashboard/settings">Settings</Link> |
        <Link to="/dashboard/profile">Profile</Link>
      </nav>
      <Outlet /> {/* 子路由会渲染在这里 */}
    </div>
  );
}

function DashboardHome() {
  return <h2>Dashboard Home</h2>;
}

function DashboardSettings() {
  return <h2>Dashboard Settings</h2>;
}

function DashboardProfile() {
  return <h2>Dashboard Profile</h2>;
}

编程式导航

使用 useNavigate

javascript
import { useNavigate } from 'react-router-dom';

function Login() {
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    // 登录逻辑
    navigate('/dashboard');
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Login</button>
    </form>
  );
}

路由守卫

使用 Navigate 组件

javascript
import { Routes, Route, Navigate } from 'react-router-dom';

function App() {
  const isAuthenticated = false; // 模拟认证状态

  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<Login />} />
      <Route 
        path="/dashboard" 
        element={isAuthenticated ? <Dashboard /> : <Navigate to="/login" />} 
      />
    </Routes>
  );
}

总结

React Router 是 React 中实现单页应用路由的核心库,它提供了丰富的功能,包括基本路由、动态路由、嵌套路由、编程式导航和路由守卫等。通过 React Router,可以构建具有良好用户体验的单页应用。

11. React 中的代码分割和懒加载

Details

代码分割和懒加载是 React 中优化应用性能的重要技术,它们可以减少初始加载时间,提高用户体验。

代码分割

定义:代码分割是将应用代码分割成多个小块,只在需要时加载。

方法

  1. 动态 import:使用 import() 语法动态加载模块。
  2. React.lazy:结合动态 import 实现组件的懒加载。
  3. React.Suspense:用于显示加载状态,配合 React.lazy 使用。

组件懒加载

示例

javascript
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

// 懒加载组件
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const Contact = lazy(() => import('./Contact'));

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> |
        <Link to="/about">About</Link> |
        <Link to="/contact">Contact</Link>
      </nav>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

路由级代码分割

示例

javascript
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

// 按路由分割代码
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
const Profile = lazy(() => import('./Profile'));

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link> |
        <Link to="/dashboard">Dashboard</Link> |
        <Link to="/settings">Settings</Link> |
        <Link to="/profile">Profile</Link>
      </nav>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="/profile" element={<Profile />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

条件懒加载

示例

javascript
import { lazy, Suspense, useState } from 'react';

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

function App() {
  const [showHeavyComponent, setShowHeavyComponent] = useState(false);

  return (
    <div>
      <button onClick={() => setShowHeavyComponent(true)}>
        Load Heavy Component
      </button>
      {showHeavyComponent && (
        <Suspense fallback={<div>Loading...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  );
}

总结

代码分割和懒加载是 React 中优化应用性能的重要技术,它们可以减少初始加载时间,提高用户体验。通过动态 import、React.lazy 和 React.Suspense,可以实现组件和路由的懒加载,从而优化应用的性能。

12. React 中的服务器端渲染(SSR)和静态站点生成(SSG)

Details

服务器端渲染(SSR)和静态站点生成(SSG)是 React 中优化首屏加载性能和 SEO 的重要技术。

服务器端渲染(SSR)

定义:服务器端渲染是指在服务器端生成 HTML,然后将完整的 HTML 发送到客户端。

优点

  • 提高首屏加载速度
  • 改善 SEO
  • 减少客户端的计算负担

框架:Next.js、Gatsby(部分支持)

Next.js 示例

javascript
// pages/index.js
export default function Home({ data }) {
  return (
    <div>
      <h1>Server-Side Rendering</h1>
      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getServerSideProps() {
  // 在服务器端获取数据
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: { data }
  };
}

静态站点生成(SSG)

定义:静态站点生成是指在构建时生成 HTML,然后将静态 HTML 文件部署到服务器。

优点

  • 极高的首屏加载速度
  • 改善 SEO
  • 减少服务器负担
  • 可以部署到 CDN

框架:Next.js、Gatsby

Next.js 示例

javascript
// pages/index.js
export default function Home({ data }) {
  return (
    <div>
      <h1>Static Site Generation</h1>
      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  // 在构建时获取数据
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: { data }
  };
}

增量静态再生(ISR)

定义:增量静态再生是 Next.js 中的一个特性,允许在构建后更新静态页面。

优点

  • 结合了 SSG 和 SSR 的优点
  • 可以定期更新静态页面
  • 减少构建时间

Next.js 示例

javascript
// pages/index.js
export default function Home({ data }) {
  return (
    <div>
      <h1>Incremental Static Regeneration</h1>
      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: { data },
    revalidate: 60 // 每 60 秒重新生成页面
  };
}

总结

服务器端渲染(SSR)和静态站点生成(SSG)是 React 中优化首屏加载性能和 SEO 的重要技术。SSR 适合需要实时数据的场景,而 SSG 适合数据变化不频繁的场景。增量静态再生(ISR)则结合了两者的优点,是一种更灵活的方案。

13. React 18 的新特性

Details

React 18 引入了许多新特性,包括并发渲染、自动批处理、新的 Suspense 功能等。

1. 并发渲染

定义:并发渲染是 React 18 中的核心特性,它允许 React 同时处理多个任务,提高应用的响应速度和用户体验。

优点

  • 提高应用的响应速度
  • 改善用户体验
  • 支持优先级调度

2. 自动批处理

定义:自动批处理是指 React 18 会自动将多个状态更新合并为一个渲染周期,减少不必要的渲染。

示例

javascript
// React 17 中,只有在事件处理函数中的状态更新会被批处理
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // 只会渲染一次
}

// 在 React 17 中,Promise 或 setTimeout 中的状态更新不会被批处理
function handleClick() {
  fetch('/api').then(() => {
    setCount(c => c + 1);
    setFlag(f => !f);
    // 会渲染两次
  });
}

// React 18 中,所有状态更新都会被批处理
function handleClick() {
  fetch('/api').then(() => {
    setCount(c => c + 1);
    setFlag(f => !f);
    // 只会渲染一次
  });
}

3. 新的 Suspense 功能

定义:React 18 增强了 Suspense 功能,支持在服务器端渲染中使用 Suspense。

示例

javascript
// pages/index.js
import { Suspense } from 'react';
import { lazy } from 'react';

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

export default function Home() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

4. 新的 Hooks

useId:生成唯一的 ID,用于服务端渲染和客户端渲染的一致性。

javascript
import { useId } from 'react';

function Component() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>Input:</label>
      <input id={id} type="text" />
    </div>
  );
}

useTransition:标记一个状态更新为低优先级,允许 React 优先处理其他更新。

javascript
import { useTransition, useState } from 'react';

function Component() {
  const [isPending, startTransition] = useTransition();
  const [input, setInput] = useState('');
  const [filteredList, setFilteredList] = useState([]);

  function handleInputChange(e) {
    setInput(e.target.value);
    startTransition(() => {
      // 低优先级更新
      setFilteredList(filterItems(e.target.value));
    });
  }

  return (
    <div>
      <input value={input} onChange={handleInputChange} />
      {isPending && <div>Loading...</div>}
      <ul>
        {filteredList.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

useDeferredValue:延迟一个值的更新,直到其他更新完成。

javascript
import { useDeferredValue, useState } from 'react';

function Component() {
  const [input, setInput] = useState('');
  const deferredInput = useDeferredValue(input);

  return (
    <div>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <HeavyComponent value={deferredInput} />
    </div>
  );
}

5. 服务器端渲染改进

React 18 改进了服务器端渲染的性能和可靠性,包括:

  • 支持 Suspense 服务器端渲染
  • 流式渲染
  • 更好的错误处理

总结

React 18 引入了许多新特性,包括并发渲染、自动批处理、新的 Suspense 功能和新的 Hooks 等。这些特性提高了应用的性能和用户体验,是构建现代 React 应用的重要工具。