jsonz1993 / react-source-learn

react16 源码阅读学习记录
151 stars 24 forks source link

React Hooks食用指南(三): useEffect 与实践 #8

Open jsonz1993 opened 5 years ago

jsonz1993 commented 5 years ago

这篇讲一下目前接触最多的Hook: useEffect,期间也会穿插一些用到的Hooks。

一般我们会用它来请求数据或者模拟生命周期里面的一些操作,对于模拟生命周期的操作,我们之前有讲过,可以参考第二篇

这里我我们主要以CNode的接口为例子写个demo,获取某个主题下面的帖子列表并展示出来


基本的Demo

常规操作,在CDM里面调用接口,然后setState渲染出来,最简单的demo就写好了

live demo1 下拉框:基础列表 hook3_demo1

这里扯个点,useEffect 不可以是async的形式,即useEffect(async () => {})。 因为useEffect会将返回的函数作为清除时调用,而async默认就会返回一个Promise,这明显不是我们想要的结果,所以如果这么写React会给你一个警告。


参数变换

但是一般我们的业务都不是这么简单,最起码有多个主题,你好歹得给别人切换一下主题吧?

如果是Class,我们一般会有一个下拉框,然后用户选择的时候setState(type)去改变用户当前选择的主题,再在CDU(ComponentDidUpdate)里面判断this.state.type !== prevState.type去调接口更新数据

如果按着这样翻译到Hooks的话,就是useEffect(() => 调接口, [type]),但是我不建议大家还是以Class的思维来写Hooks,这样每次写一个功能首先想到的是Class怎么实现,然后再想着怎么翻译成Hooks,吃力不讨好。

我们写Hooks或者说成FunctionC的时候,不要一直想着this.state或者DidUpdate之类的。而是类似Observable这种思想: 监听type,如果type变化,那我需要重新去获取接口请求。

这里在useEffect的函数体依赖到了外部state:type,所以第二个参数加入我们函数里依赖到的type。

live demo2 下拉框:提供类型选择 hook3_demo2


useEffect依赖问题

这里我们扩展一下Effect第二个参数依赖的问题。 写useEffect有一个规则 不要欺骗effect,除非特殊情况,否则用到什么依赖就写什么 不然很容易出bug。 那么FunC有一种场景就是,如果我们依赖列表里面有函数怎么处理?

因为如果是组件内部写的函数的话,每次render其实都会重新生成一个函数,这就意味着你如果依赖他,那每次render都会跑effect。

用Hooks的话,我们可以借助 useCallback、useMemo、memo处理这种问题。 memo相当于是PureComponent这里不多赘述。

useMemo,useCallback

useMemo其实就是提供一个memoized功能的Hook,使用也简单。 useCallback就是返回值为函数版本的useMemo

live demo3 hook3_demo3


loading与报错等通用接口调用封装

回到我们的需求来,我们这里已经把对应的接口获取封装好了,业务上一般都会对接口做基本的报错处理和loading处理。

错误处理还好,现在我们大多用 try...catch 直接在 catch 捕获一个error信息

const [errorMsg, setError] = useState('')

try {
  ...
} catch(e) {
  setError(e.message)
}

同理loading也直接用一个 state 去存起来,不过我们应该把他放在finally

const [loading, updateLoading] = useState(false)
try {
  setLoading(true)
  async...
} finally {
  setLoading(false)
}

demo live4 下拉框: loading与错误处理 hook3_demo4


custom Hooks

从上面的图我们可以明显看出,代码里面有很多的useState,过于分散,如果有一些state有依赖的话就更不好处理。 而且我们的接口函数也写在Hook里面,这样很难复用到其他的接口请求,还有就是异步请求很容易有竟态问题。

首先,我们把state都抽离出来,用useReducer来管理(类redux) hook3_demo5_1

然后,把获取数据的函数也抽离出来,Hooks内部用一个useRef去保存最新的调用方法 hook3_demo5_2

最后把对应的state返回回来,用到该hooks的组件可以直接拿来就用不用关心内部是怎么维护这一份state的,而且 这种提供局部state抽离的模式,用ClassC我没想到怎么实现,这也是FunC最吸引我的一点。

const [posts, postsLoading, postsError] = useService(type, serverGetPosts)

demo live5 下拉框: 独立封装

到这里我们已经封装好一个包含loading、错误处理的接口获取Hooks,当然里面还有很多可以根据自己需要改动的。

这只是一个用来了解Hooks的一个例子,业务上我们可能更多的是使用redux/rematch,loading也会有对应的plugin直接拿来就用。

到这里关于useEffect使用及一些常见问题讲得七七八八了


最后讲一下目前项目上实践中遇到的一些小问题,一开始用Hooks的时候并没有了解太多,纯粹觉得这个东西比较好玩又是趋势,刚好碰到要做个新项目就在部分页面用FunC+Hook代替ClassC的写法。

所以多多少少会吃文化上的亏,不过如果这几篇看下来应该小问题都还好,至少概念和用法场景都会比较清晰。

问题:用useEffect初始化某些配置

场景是这样的,有一个表格的 columns 配置,里面有一些是涉及到组件dispatch等的操作,所以你没办法把它抽离到最外层,只能丢在组件内。

但是我又有点强迫症,不想每一次渲染都去重新赋值(里面涉及到一些计算),想着平时这种情况,我都是写在 CDM 里面,于是就有下面的代码

hook3_demo6

这里为什么不生效呢? 大家心里应该都有答案,也正是碰到这种"奇奇怪怪"的问题之后,我才会想到需要系统一点去了解Hooks的东西,也才有这个系列的水文

问题:Hooks最佳实现与代码过长问题

这个问题困扰我挺久的,不过不关Hooks事可能说成是FunC的问题会贴切一点。 写ClassC的时候,你可以很好的把方法逻辑拆分为Class的方法属性,清晰明了很多。 约定方法的顺序,以及命名之后,感觉要找逻辑或者划分功能非常容易 hook3_demo7

但是到了Func就会总觉得一个函数体太大太长,而且个人不大喜欢函数里面嵌一堆的函数,然后共用一份变量,类似这样 条理没有ClassC那么好,而且其实Hooks抽离有一定的难度,你要对业务足够熟悉,知道哪一些有可能公用才能抽出来。 hook3_demo8

绝大多数情况,前端开发的时候,可能连文档都还没给全甚至设计图都没给就开撸,你都不知道后面会有什么其他的逻辑可以公用,一不小心又可能变成过度设计(过早优化)。

不过还是很看好Hooks,毕竟这东西就是想推我们从ClassC走向FunC,这与React的编程模型更加贴切 React is a UI Runtime: data => UI

esling-react-hook

以上说到的很多关于Hooks的问题,比如依赖缺失、在useEffect初始化外部变量、没有写在最顶层等问题,都可以通过eslint-plugin-react-hooks 去发现,所以尽早给你的项目加上吧,谁用谁知道。

后面可能会继续在项目里面尝试FunC+Hooks的实践,也可能会等社区尝试出最佳实践再继续写。

最后附上学习过程看到的不错的网站 How to fetch data with React Hooks? A Complete Guide to useEffect

水完告辞

fygethub commented 4 years ago

谢谢 分享收获很大