使用React Hooks解决挫败感的方法

2020年12月30日11:20:26 发表评论 48 次浏览

本文概述

我以前的一篇文章, 对React Hooks感到沮丧获得了令人难以置信的大量观看次数黑客新闻在一个点上。帖子中也有很多评论, 其中一些改变了我对挂钩的看法, 并给了我一种全新的, 积极的观看方式。

的最后发表引用了useFetch该示例摘录了用于调用远程API端点的通用代码。一种取我希望Hooks可以使抽象成为可重用的东西。我希望加载和错误状态都包裹在一个Hook中, 就像我们以前对Redux中间件所做的那样。以下是我要为客户端代码编写的示例:

const asyncTask = useFetch(initialPage);
useAsyncRun(asyncTask);

const { start, loading, result: users } = asyncTask;

if (loading) {
  return <div>loading....</div>;
}

return (
  <>
    {(users || []).map((u: User) => (
      <div key={u.id}>{u.name}</div>
    ))}
  </>
);

我举了一个基于反应挂钩异步有一个useFetch钩。

这里有一个CodeSandbox包含按比例缩小的示例:

这是一个代码清单:

const createTask = (func, forceUpdateRef) => {
  const task = {
    start: async (...args) => {
      task.loading = true;
      task.result = null;
      forceUpdateRef.current(func);
      try {
        task.result = await func(...args);
      } catch (e) {
        task.error = e;
      }
      task.loading = false;
      forceUpdateRef.current(func);
    }, loading: false, result: null, error: undefined
  };
  return task;
};

export const useAsyncTask = (func) => {
  const forceUpdate = useForceUpdate();
  const forceUpdateRef = useRef(forceUpdate);
  const task = useMemo(() => createTask(func, forceUpdateRef), [func]);

  useEffect(() => {
    forceUpdateRef.current = f => {
      if (f === func) {
        forceUpdate({});
      }
    };
    const cleanup = () => {
      forceUpdateRef.current = () => null;
    };
    return cleanup;
  }, [func, forceUpdate]);

  return useMemo(
    () => ({
      start: task.start, loading: task.loading, error: task.error, result: task.result
    }), [task.start, task.loading, task.error, task.result]
  );
};

许多评论提到了这种方法的复杂性, 而最有说服力的评论提到了这种实现方式不是非常声明性的。

挂钩用于可重用的生命周期行为

毫无疑问, 评论部分中的最佳评论来自凯伦·格里高良(Karen Grigoryan)他指出, Hooks是可重用的生命周期行为的场所。

反应挂钩异步还有那个例子CodeSandbox使用useAsyncRun启动生命周期更改事件的功能:

export const useAsyncRun = (asyncTask, ...args) => {
  const { start } = asyncTask;
  useEffect(() => {
    start(...args);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [asyncTask.start, ...args]);
  
useEffect(() => {
    const cleanup = () => {
      // clean up code here
    };
    return cleanup;
  });

React通常被吹捧为声明性框架, 而我之所以爱上React是一个单向数据流的故事。useAsyncRun感觉比声明更重要。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

使用React Hooks解决挫败感的方法1

React之道

React的最佳工作方式是我们更改道具或状态, 并且组件自然做出反应。

卡伦请创建CodeSandbox这不仅简化了事情, 而且使事情感到更加反感(是的, 现在是一个实际的词)和说明性的:

useFetch现在看起来像这样:

const fetchReducer: FetchReducer = (state, action) => {
  switch (action.type) {
    case "FETCH_START": {
      return { data: null, isLoading: true, error: null };
    }
    case "FETCH_SUCCESS": {
      return { data: action.payload, isLoading: false, error: null };
    }

    case "FETCH_ERROR": {
      return { data: null, isLoading: false, error: action.payload };
    }
    default:
      return state;
  }
};

export const useFetch = (initial) => {
  const [state, dispatch] = useReducer(fetchReducer, initialState);

  const getFetchResult = useCallbackOne(
    async (overrides) => {
      dispatch({ type: "FETCH_START" });
      try {
        const result = await api({ ...initial, ...overrides });
        dispatch({ type: "FETCH_SUCCESS", payload: (result as unknown) as T });
      } catch (err) {
        dispatch({ type: "FETCH_ERROR", payload: err });
      }
    }, [initial]
  );

  return [state, getFetchResult];
};

的useFetch钩在上面的代码中返回一个getFetchResult功能。getFetchResult使用调度从返回的函数useReducer协调生命周期的变化。

使用useState和useReducer是我们用来触发效果变化的一种方式, 但是是以声明的方式。强制重新渲染在React的上游进行, 并违背了React的声明性。我想我还是再次爱上了React的单向数据流。单向数据流是吸引我参与React的原因, 它仍然可以缓解繁重的JavaScript应用程序的混乱。

React应该以这种方式工作, 我们改变状态, 组件知道如何重新渲染, 并且useEffect代码块是根据状态变化而执行的。

客户端代码现在如下所示:

const [fetchResult, getfetchResult] = useFetch<User[]>(initialPage);

  const { data: users, isLoading, error } = fetchResult;

  // to keep reference identity in tact until next remount
  const defaultUsersRef = useRef<User[]>([]);

  // to kick off initial request
  useEffect(() => {
    getfetchResult(initialPage);
  }, [getfetchResult]);

  if (isLoading) {
    return <div>loading....</div>;
  }

  if (error) {
    return <div>error : {JSON.stringify(error)}</div>;
  }

  return (
    <>
      <Users users={users || defaultUsersRef.current} />
      <Knobs onClick={getfetchResult} />
    </>
  );

getFetchResult现在可以在useEffect首次安装组件时以及事件处理程序中。

非常感谢Karen的出色示例。

还值得注意的是悬念可能即将下降, 这可能是真正适合的useFetch解。

观察者会注意到getFetchResult用途useCallbackOne从使用备忘一.useCallbackOne是安全的替代品useCallback.useCallbackOne对依赖项数组的值(而不是数组引用)进行浅层检查。这仍然令React Hooks感到沮丧, 为此我们需要一个外部库, 这使我们很好地解决了过时的关闭问题。

过时的关闭问题

我一直担心因闭包而引起的闭包问题, 而在处理闭包时却没有那么奇妙的事情发生。与钩子打交道时, 关闭是生活中不可或缺的事实。下面的示例很好地说明了这种现象:

const useInterval = (callback, delay) => {
  useEffect(() => {
    let id = setInterval(() => {
    callback();
  }, 1000);
    return () => clearInterval(id);
  }, []);
};

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

 useInterval(() => setCount(count + 1), 1000);

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

这个CodeSandbox展示了这种巨大的邪恶行为:

那是怎么回事useEffect在里面useIntervalHook捕获第一个渲染的计数, 其初始值为0。的useEffect有一个空的依赖项数组, 这意味着它永远不会被重新应用并且总是引用0从第一个渲染开始, 计算总是0 + 1.

如果要使用useEffect好, 你需要确保依赖项数组包含外部作用域中随时间变化并被效果使用的所有值。

的反应钩/详尽的下降在大多数情况下, linting规则可以很好地突出显示缺少的依赖项, 它正确地指出了打回来在作为第二个参数传递给的数组中丢失useEffect:

const useInterval = (callback, delay) => {
  useEffect(() => {
    let id = setInterval(() => {
      callback();
    }, delay);
  
    return () => clearInterval(id);
  }, [callback, delay]);
};

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

  useInterval(() => setCount(count + 1), 1000);

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

我们的问题是回调传递给useInterval是一个箭头函数, 这意味着它将在每个渲染器上重新创建:

useInterval(() => setCount(count + 1), 1000);

过时关闭的一种解决方案

丹·阿布拉莫夫提出了将回调存储在可变引用中的情况这个帖子.

基于将回调存储在可变引用中的主题, 我已经看到相同解决方案出现在各种外观的多个软件包中。我以我为榜样形式提供了useEventCallbackHook负责将回调存储在可变的Hook中。

function useEventCallback(fn) {
  const ref = React.useRef(fn);

  useEffect(() => {
    ref.current = fn;
  });

  return React.useCallback(
    (...args) => ref.current.apply(void 0, args), []
  );
}

function useInterval(callback, delay) {
  const savedCallback = useEventCallback(callback);

  useEffect(() => {
    function tick() {
      savedCallback();
    }

    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

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

  useInterval(() => {
    setCount(count + 1);
  }, 1000);

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

将回调存储在可变引用中意味着最新的回调可以保存在每个渲染器的引用中。

这个CodeSandbox表演useEventCallback实际上:

总结

弯钩是一种思维转变, 我认为我们需要重新调整思维。我没有看他们不戴React眼镜所能提供的东西。挂钩非常适合React的声明性, 我认为它们是状态变化和组件知道如何对状态变化做出反应的绝佳抽象。巨大!

全面了解生产React应用

调试React应用程序可能很困难, 尤其是当用户遇到难以重现的问题时。如果你有兴趣监视和跟踪Redux状态, 自动显示JavaScript错误以及跟踪缓慢的网络请求和组件加载时间,

尝试notlogy

.

使用React Hooks解决挫败感的方法2
LogRocket仪表板免费试用横幅

日志火箭就像Web应用程序的DVR, 实际上记录了React应用程序中发生的一切。你可以汇总并报告问题发生时应用程序所处的状态, 而不用猜测为什么会发生问题。 notlogy还监视你的应用程序的性能, 并使用客户端CPU负载, 客户端内存使用情况等指标进行报告。

notlogy Redux中间件软件包在你的用户会话中增加了一层可见性。 notlogy记录Redux存储中的所有操作和状态。

现代化如何调试React应用程序-免费开始监控.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: