Hooks最佳实践 #

一、Hooks规则 #

1.1 只在最顶层调用Hook #

javascript
// ❌ 错误:在条件语句中调用
function Component({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null); // 错误
  }
  
  return <div>...</div>;
}

// ✅ 正确:在顶层调用
function Component({ isLoggedIn }) {
  const [user, setUser] = useState(null);
  
  if (isLoggedIn) {
    // 使用user
  }
  
  return <div>...</div>;
}

1.2 只在React函数中调用Hook #

javascript
// ✅ 在函数组件中调用
function Component() {
  const [state, setState] = useState(0);
}

// ✅ 在自定义Hook中调用
function useCustomHook() {
  const [state, setState] = useState(0);
}

// ❌ 不要在普通函数中调用
function handleClick() {
  const [state, setState] = useState(0); // 错误
}

// ❌ 不要在类组件中调用
class Component extends React.Component {
  render() {
    const [state, setState] = useState(0); // 错误
  }
}

1.3 使用ESLint插件 #

javascript
// 安装
// npm install eslint-plugin-react-hooks --save-dev

// .eslintrc配置
{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

二、依赖管理 #

2.1 正确设置依赖 #

javascript
// ❌ 缺少依赖
useEffect(() => {
  fetchData(userId);
}, []); // userId变化时不会重新执行

// ✅ 包含所有依赖
useEffect(() => {
  fetchData(userId);
}, [userId]);

2.2 函数依赖 #

javascript
// ❌ 函数作为依赖会导致频繁执行
function Component() {
  const fetchData = () => {
    console.log('fetching');
  };

  useEffect(() => {
    fetchData();
  }, [fetchData]); // fetchData每次渲染都是新引用
}

// ✅ 使用useCallback
function Component() {
  const fetchData = useCallback(() => {
    console.log('fetching');
  }, []);

  useEffect(() => {
    fetchData();
  }, [fetchData]);
}

// ✅ 或者将函数移到useEffect内部
function Component() {
  useEffect(() => {
    const fetchData = () => {
      console.log('fetching');
    };
    fetchData();
  }, []);
}

2.3 对象依赖 #

javascript
// ❌ 对象作为依赖会导致频繁执行
function Component({ options }) {
  useEffect(() => {
    doSomething(options);
  }, [options]); // options每次渲染都是新对象
}

// ✅ 使用useMemo
function Component({ options }) {
  const memoizedOptions = useMemo(() => options, [JSON.stringify(options)]);

  useEffect(() => {
    doSomething(memoizedOptions);
  }, [memoizedOptions]);
}

// ✅ 或者解构依赖
function Component({ options }) {
  const { a, b } = options;

  useEffect(() => {
    doSomething({ a, b });
  }, [a, b]);
}

三、性能优化 #

3.1 使用useMemo缓存计算 #

javascript
// ❌ 每次渲染都重新计算
function Component({ list }) {
  const sortedList = list.sort((a, b) => a - b);
  
  return <List data={sortedList} />;
}

// ✅ 使用useMemo缓存
function Component({ list }) {
  const sortedList = useMemo(
    () => [...list].sort((a, b) => a - b),
    [list]
  );
  
  return <List data={sortedList} />;
}

3.2 使用useCallback缓存函数 #

javascript
// ❌ 每次渲染创建新函数
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    console.log('clicked');
  };

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

// ✅ 使用useCallback
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

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

3.3 合理使用React.memo #

javascript
const Child = React.memo(function Child({ onClick, data }) {
  console.log('Child render');
  return <button onClick={onClick}>{data}</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  const data = useMemo(() => 'Button', []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <Child onClick={handleClick} data={data} />
    </div>
  );
}

3.4 避免过度优化 #

javascript
// ❌ 过度优化:简单计算不需要useMemo
const doubled = useMemo(() => count * 2, [count]);

// ✅ 简单计算直接计算
const doubled = count * 2;

// ✅ 复杂计算使用useMemo
const sortedList = useMemo(() => {
  return [...list].sort((a, b) => {
    // 复杂排序逻辑
  });
}, [list]);

四、状态管理 #

4.1 状态最小化 #

javascript
// ❌ 冗余状态
function Component() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState(''); // 冗余

  useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);
}

// ✅ 计算派生状态
function Component() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  
  const fullName = `${firstName} ${lastName}`;
}

4.2 状态就近原则 #

javascript
// ✅ 状态放在使用它的最近父组件
function TodoItem({ todo }) {
  const [isEditing, setIsEditing] = useState(false);
  
  return (
    <div>
      {isEditing ? <EditForm /> : <TodoDisplay />}
    </div>
  );
}

4.3 使用函数式更新 #

javascript
// ❌ 依赖旧状态
const increment = () => {
  setCount(count + 1);
  setCount(count + 1);
  // 结果:count只增加1
};

// ✅ 使用函数式更新
const increment = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  // 结果:count增加2
};

五、副作用处理 #

5.1 及时清理副作用 #

javascript
// ✅ 清理定时器
useEffect(() => {
  const timer = setInterval(() => {
    console.log('tick');
  }, 1000);

  return () => clearInterval(timer);
}, []);

// ✅ 清理事件监听
useEffect(() => {
  const handleResize = () => {
    console.log('resize');
  };

  window.addEventListener('resize', handleResize);

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

5.2 处理竞态条件 #

javascript
// ✅ 使用标志位
useEffect(() => {
  let isMounted = true;

  fetch(url)
    .then(res => res.json())
    .then(data => {
      if (isMounted) {
        setData(data);
      }
    });

  return () => {
    isMounted = false;
  };
}, [url]);

// ✅ 使用AbortController
useEffect(() => {
  const controller = new AbortController();

  fetch(url, { signal: controller.signal })
    .then(res => res.json())
    .then(setData)
    .catch(err => {
      if (err.name !== 'AbortError') {
        setError(err);
      }
    });

  return () => controller.abort();
}, [url]);

5.3 避免在渲染中产生副作用 #

javascript
// ❌ 在渲染中产生副作用
function Component() {
  const [count, setCount] = useState(0);
  
  if (count > 10) {
    setCount(0); // 副作用
  }
  
  return <div>{count}</div>;
}

// ✅ 使用useEffect
function Component() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    if (count > 10) {
      setCount(0);
    }
  }, [count]);

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

六、常见陷阱 #

6.1 闭包陷阱 #

javascript
// ❌ 闭包陷阱
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // 永远是0
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// ✅ 使用ref解决
function Counter() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);
  countRef.current = count;

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(countRef.current);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

6.2 无限循环 #

javascript
// ❌ 无限循环
function Component() {
  const [data, setData] = useState([]);

  useEffect(() => {
    setData([...data, newItem]); // 触发重新渲染
  }, [data]); // data变化又触发useEffect
}

// ✅ 使用函数式更新
function Component() {
  const [data, setData] = useState([]);

  useEffect(() => {
    setData(prev => [...prev, newItem]);
  }, []); // 只执行一次
}

6.3 不正确的初始化 #

javascript
// ❌ 每次渲染都执行昂贵计算
const [state, setState] = useState(expensiveComputation());

// ✅ 使用惰性初始化
const [state, setState] = useState(() => expensiveComputation());

七、代码组织 #

7.1 相关状态放在一起 #

javascript
// ✅ 相关状态组合
function Form() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: ''
  });
}

// 或使用useReducer
function Form() {
  const [state, dispatch] = useReducer(formReducer, initialState);
}

7.2 抽取自定义Hook #

javascript
// ❌ 组件逻辑复杂
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 获取用户逻辑...
  }, [userId]);

  // 其他逻辑...
}

// ✅ 抽取自定义Hook
function useUser(userId) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 获取用户逻辑...
  }, [userId]);

  return { user, loading, error };
}

function UserProfile({ userId }) {
  const { user, loading, error } = useUser(userId);
  
  if (loading) return <Loading />;
  if (error) return <Error error={error} />;
  
  return <UserCard user={user} />;
}

八、最佳实践清单 #

8.1 规则清单 #

规则 说明
顶层调用 不在条件、循环中调用Hook
React函数 只在组件或自定义Hook中调用
依赖完整 useEffect依赖数组完整

8.2 性能清单 #

优化 说明
useMemo 缓存复杂计算结果
useCallback 缓存传递给子组件的函数
React.memo 避免子组件不必要渲染
惰性初始化 避免重复执行昂贵计算

8.3 状态清单 #

实践 说明
最小化 避免冗余状态
就近原则 状态放在最近父组件
函数式更新 避免依赖旧状态

九、总结 #

类别 要点
规则 顶层调用、React函数中调用
依赖 正确设置、避免遗漏
性能 useMemo、useCallback、React.memo
状态 最小化、就近原则、函数式更新
副作用 及时清理、处理竞态

核心原则:

  • 遵循 Hooks 规则
  • 正确管理依赖
  • 合理优化性能
  • 保持代码简洁
最后更新:2026-03-26