react native项目中使用React Hook 高级模式

React Hook 高级模式:从触发器模式到实战应用

前言

在 React 开发中,我们经常遇到这样的场景:父组件需要控制子组件重新加载数据,但又不想重新挂载整个组件。本文将介绍一种优雅的解决方案------触发器模式(Trigger Pattern),以及其他 9 种实用的 Hook 模式。

问题场景

假设我们有一个图片上传组件 FileUpload,在编辑页面修改图片后返回列表页,需要刷新图片显示:

javascript 复制代码
// 列表页
function RecordsList() {
  const refresh = () => {
    fetchData(); // 刷新列表数据
    // 问题:如何让 FileUpload 组件也刷新?
  };
  
  return (
    <div>
      {data.map(item => (
        <FileUpload id={item.id} /> // 需要刷新图片
      ))}
    </div>
  );
}

常见的错误方案

❌ 方案 1:使用 key 强制重新挂载
javascript 复制代码
<FileUpload key={`${item.id}-${Date.now()}`} id={item.id} />

问题:

  • 组件完全销毁重建,丢失所有内部状态
  • 性能差,会触发完整的挂载/卸载生命周期
  • 可能导致闪烁或动画中断
❌ 方案 2:通过 ref 调用子组件方法
javascript 复制代码
const fileUploadRef = useRef();

const refresh = () => {
  fileUploadRef.current.refresh(); // 命令式调用
};

<FileUpload ref={fileUploadRef} id={item.id} />

问题:

  • 打破了 React 的单向数据流
  • 不符合声明式编程理念
  • 难以追踪数据流向
❌ 方案 3:使用布尔值切换
javascript 复制代码
const [shouldRefresh, setShouldRefresh] = useState(false);

const refresh = () => {
  setShouldRefresh(true);
  setTimeout(() => setShouldRefresh(false), 0);
};

<FileUpload shouldRefresh={shouldRefresh} />

问题:

  • 需要复杂的状态重置逻辑
  • 连续刷新可能失效(值没有变化)
  • 代码不够优雅

✅ 最佳方案:触发器模式

核心思想

用一个简单的数值变化来触发复杂的副作用

实现步骤

1. 创建 Hook
javascript 复制代码
// src/hooks/useRefreshTrigger.js
import { useState, useCallback } from 'react';

export const useRefreshTrigger = () => {
  const [trigger, setTrigger] = useState(0);
  
  const fire = useCallback(() => {
    setTrigger(prev => prev + 1);
  }, []);
  
  return [trigger, fire];
};
2. 在父组件中使用
javascript 复制代码
// 列表页
import { useRefreshTrigger } from '@/hooks/useRefreshTrigger';

function RecordsList() {
  const [refreshTrigger, triggerRefresh] = useRefreshTrigger();
  
  const refresh = () => {
    fetchData();
    triggerRefresh(); // 触发刷新
  };
  
  return (
    <div>
      {data.map(item => (
        <FileUpload 
          id={item.id} 
          refreshTrigger={refreshTrigger} // 传递触发器
        />
      ))}
    </div>
  );
}
3. 在子组件中响应
javascript 复制代码
// FileUpload 组件
function FileUpload({ id, refreshTrigger }) {
  const [pictures, setPictures] = useState([]);
  
  useEffect(() => {
    if (id) {
      // 当 id 或 refreshTrigger 变化时,重新加载图片
      fetchImages(id).then(setPictures);
    }
  }, [id, refreshTrigger]); // ← 关键:依赖 refreshTrigger
  
  return (
    <div>
      {pictures.map(pic => <img src={pic.url} />)}
    </div>
  );
}

工作原理

scss 复制代码
用户操作 → triggerRefresh() → trigger: 0 → 1 → 2 → ...
                                    ↓
                        useEffect 检测到依赖变化
                                    ↓
                            重新执行副作用

优势总结

声明式 - 符合 React 理念,数据流清晰

轻量级 - 只触发 effect,不重新挂载组件

可预测 - 父组件控制,子组件响应

可复用 - Hook 可在任何场景使用

性能好 - 不会丢失组件状态


其他 9 种实用 Hook 模式

1. Toggle Pattern - 开关模式

管理布尔状态的切换,比 useState 更方便:

javascript 复制代码
export const useToggle = (initialValue = false) => {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => setValue(v => !v), []);
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);
  
  return [value, { toggle, setTrue, setFalse }];
};

// 使用
function Modal() {
  const [isOpen, { toggle, setTrue, setFalse }] = useToggle(false);
  
  return (
    <>
      <button onClick={setTrue}>打开</button>
      <Dialog visible={isOpen} onClose={setFalse} />
    </>
  );
}

适用场景: 模态框、折叠面板、显示/隐藏


2. Previous Value Pattern - 记录上一次的值

获取状态的前一个值,用于对比:

javascript 复制代码
export const usePrevious = (value) => {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  }, [value]);
  
  return ref.current;
};

// 使用
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  
  return (
    <div>
      <p>当前: {count}</p>
      <p>上一次: {prevCount}</p>
      <p>变化: {count - (prevCount || 0)}</p>
    </div>
  );
}

适用场景: 对比新旧值、撤销操作、动画过渡


3. Debounce Pattern - 防抖模式

延迟执行,避免频繁触发:

javascript 复制代码
export const useDebounce = (value, delay = 500) => {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(timer);
  }, [value, delay]);
  
  return debouncedValue;
};

// 使用
function SearchBox() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  
  useEffect(() => {
    if (debouncedSearchTerm) {
      searchAPI(debouncedSearchTerm); // 只在停止输入 500ms 后搜索
    }
  }, [debouncedSearchTerm]);
  
  return <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />;
}

适用场景: 搜索输入、窗口 resize、滚动事件


4. Async State Pattern - 异步状态管理

统一管理异步请求的加载、成功、失败状态:

javascript 复制代码
export const useAsync = (asyncFunction) => {
  const [state, setState] = useState({
    loading: false,
    data: null,
    error: null,
  });
  
  const execute = useCallback(async (...params) => {
    setState({ loading: true, data: null, error: null });
    
    try {
      const data = await asyncFunction(...params);
      setState({ loading: false, data, error: null });
      return data;
    } catch (error) {
      setState({ loading: false, data: null, error });
      throw error;
    }
  }, [asyncFunction]);
  
  return { ...state, execute };
};

// 使用
function UserProfile({ userId }) {
  const { loading, data, error, execute } = useAsync(fetchUserData);
  
  useEffect(() => {
    execute(userId);
  }, [userId]);
  
  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
  
  return <div>用户名: {data.name}</div>;
}

适用场景: API 调用、数据加载


5. Local Storage Pattern - 持久化状态

自动同步状态到 localStorage:

javascript 复制代码
export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  const setValue = useCallback((value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);
  
  return [storedValue, setValue];
};

// 使用
function ThemeSelector() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      当前主题: {theme}
    </button>
  );
}

适用场景: 用户偏好设置、表单草稿、主题切换


6. Interval Pattern - 定时器模式

安全地使用 setInterval:

javascript 复制代码
export const useInterval = (callback, delay) => {
  const savedCallback = useRef();
  
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  
  useEffect(() => {
    if (delay !== null) {
      const id = setInterval(() => savedCallback.current(), delay);
      return () => clearInterval(id);
    }
  }, [delay]);
};

// 使用
function Countdown() {
  const [count, setCount] = useState(60);
  
  useInterval(() => {
    if (count > 0) setCount(count - 1);
  }, 1000);
  
  return <div>倒计时: {count}秒</div>;
}

适用场景: 倒计时、轮询、实时更新


7. Mount Status Pattern - 组件挂载状态

避免在组件卸载后更新状态(防止内存泄漏):

javascript 复制代码
export const useIsMounted = () => {
  const isMounted = useRef(false);
  
  useEffect(() => {
    isMounted.current = true;
    return () => {
      isMounted.current = false;
    };
  }, []);
  
  return useCallback(() => isMounted.current, []);
};

// 使用
function SafeAsyncComponent() {
  const [data, setData] = useState(null);
  const isMounted = useIsMounted();
  
  useEffect(() => {
    fetchData().then(result => {
      if (isMounted()) { // 只在组件还挂载时才更新
        setData(result);
      }
    });
  }, []);
  
  return <div>{data}</div>;
}

适用场景: 异步操作、延迟更新


8. Composite Pattern - 组合模式

将多个相关的 Hook 组合成一个业务 Hook:

javascript 复制代码
export const useSearchableList = (fetchFunction) => {
  const [searchTerm, setSearchTerm] = useState('');
  const [refreshTrigger, triggerRefresh] = useRefreshTrigger();
  const debouncedSearchTerm = useDebounce(searchTerm, 300);
  const { loading, data, error, execute } = useAsync(fetchFunction);
  
  useEffect(() => {
    execute(debouncedSearchTerm);
  }, [debouncedSearchTerm, refreshTrigger]);
  
  return {
    searchTerm,
    setSearchTerm,
    loading,
    data,
    error,
    refresh: triggerRefresh,
  };
};

// 使用
function ProductList() {
  const { searchTerm, setSearchTerm, loading, data, refresh } = 
    useSearchableList(searchProducts);
  
  return (
    <div>
      <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
      <button onClick={refresh}>刷新</button>
      {loading ? '加载中...' : data?.map(item => <div key={item.id}>{item.name}</div>)}
    </div>
  );
}

适用场景: 表单管理、分页列表、搜索功能


9. Event Listener Pattern - 事件监听模式

安全地添加和移除事件监听器:

javascript 复制代码
export const useEventListener = (eventName, handler, element = window) => {
  const savedHandler = useRef();
  
  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);
  
  useEffect(() => {
    const isSupported = element && element.addEventListener;
    if (!isSupported) return;
    
    const eventListener = (event) => savedHandler.current(event);
    element.addEventListener(eventName, eventListener);
    
    return () => {
      element.removeEventListener(eventName, eventListener);
    };
  }, [eventName, element]);
};

// 使用
function KeyboardShortcuts() {
  useEventListener('keydown', (e) => {
    if (e.ctrlKey && e.key === 's') {
      e.preventDefault();
      console.log('保存快捷键触发');
    }
  });
  
  return <div>按 Ctrl+S 保存</div>;
}

适用场景: 键盘事件、窗口事件、自定义事件


Hook 设计原则

1. 单一职责原则

每个 Hook 只做一件事,保持简单和专注。

2. 可组合性

小 Hook 可以组合成大 Hook,就像乐高积木。

3. 声明式

通过 props 和返回值传递数据,避免命令式调用。

4. 可测试性

逻辑独立,易于单元测试。

5. 可复用性

不依赖具体业务,通用性强。


命名规范

Hook 命名

  • 格式:use + 功能描述(驼峰命名)
  • 示例:useRefreshTrigger, useToggle, useDebounce

返回值规范

  • 单个值:直接返回

    javascript 复制代码
    const debouncedValue = useDebounce(value);
  • 两个值:返回数组(类似 useState)

    javascript 复制代码
    const [trigger, fire] = useRefreshTrigger();
    const [isOpen, { toggle }] = useToggle();
  • 多个值:返回对象

    javascript 复制代码
    const { loading, data, error, execute } = useAsync(fetchData);

实战案例:完整的列表刷新方案

结合多个 Hook 模式,实现一个完整的列表页:

javascript 复制代码
import { useRefreshTrigger } from '@/hooks/useRefreshTrigger';
import { useDebounce } from '@/hooks/useDebounce';
import { useAsync } from '@/hooks/useAsync';

function QualityInspectionRecords({ route, navigation }) {
  // 1. 刷新触发器
  const [refreshTrigger, triggerRefresh] = useRefreshTrigger();
  
  // 2. 搜索防抖
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 300);
  
  // 3. 异步数据加载
  const { loading, data, execute } = useAsync(fetchRecords);
  
  // 4. 加载数据
  useEffect(() => {
    execute(route.params?.outsourcingId, debouncedSearchTerm);
  }, [route.params?.outsourcingId, debouncedSearchTerm, refreshTrigger]);
  
  // 5. 刷新函数
  const refresh = useCallback(() => {
    triggerRefresh();
  }, [triggerRefresh]);
  
  // 6. 渲染
  return (
    <View>
      <SearchBar value={searchTerm} onChange={setSearchTerm} />
      
      {loading ? (
        <Loading />
      ) : (
        <FlatList
          data={data}
          renderItem={({ item }) => (
            <View>
              <FileUpload 
                id={item.id} 
                refreshTrigger={refreshTrigger} // 传递触发器
              />
              <Button 
                onPress={() => navigation.navigate('Edit', {
                  item,
                  onBack: refresh, // 编辑完成后刷新
                })}
              >
                编辑
              </Button>
            </View>
          )}
        />
      )}
    </View>
  );
}

总结

Hook 模式的核心是关注点分离逻辑复用。通过将常见的状态逻辑封装成 Hook,我们可以:

  1. ✅ 减少重复代码
  2. ✅ 提高代码可读性
  3. ✅ 简化组件逻辑
  4. ✅ 提升可维护性
  5. ✅ 便于单元测试

触发器模式只是其中一种,掌握这些模式后,你会发现 React 开发变得更加优雅和高效。


参考资源


作者: [你的名字]
日期: 2026-01-10
标签: React, Hooks, 设计模式, 最佳实践

相关推荐
wayne2141 天前
React Native 状态管理方案全梳理:Redux、Zustand、React Query 如何选
javascript·react native·react.js
Mintopia2 天前
🎙️ React Native(RN)语音输入场景全解析
android·react native·aigc
程序员Agions2 天前
React Native 邪修秘籍:在崩溃边缘疯狂试探的艺术
react native·react.js
chao_6666663 天前
React Native + Expo 开发指南:编译、调试、构建全解析
javascript·react native·react.js
_pengliang3 天前
react native ios 2个modal第二个不显示
javascript·react native·react.js
wayne2143 天前
React Native 0.80 学习参考:一个完整可运行的实战项目
学习·react native·react.js
坚果派·白晓明4 天前
Windows 11 OpenHarmony版React Native开发环境搭建完整指南
react native·开源鸿蒙·rnoh
开心不就得了5 天前
React Native对接Sunmi打印sdk
javascript·react native·react.js
Joyee6915 天前
RN 的初版架构——运行时异常与异常捕获处理
react native·前端框架