前言
Hooks属于React版本的一个新特性,其目的是全面拥抱函数,之前函数式组仅仅是作为展示组件,它的内部并不关心数据是怎么加载和变动的,只能通过props的方式接收和进行callback操作,也并不具备calss组件相似的生命周期,所以函数式组件的使用场景很少。
自16.8版本起,React就开始全面提供了hooks的稳定版,使开发者在不使用calss组件的情况下,拥有自己的生命周期和状态更新机制,从此函数式组件的地位直线提升。
它的发起人是Sebastian Markbåge(如图),除了抹平class组件和function组件在生命周期的差异外,按他本人的说法,其目的是为了践行`代数效用(Algebraic Effects),那什么是代数效应呢?
代数效应
什么是代数效应?
代数效应属于函数式编程的一个概念,它的目的是将副作用自函数调用中分离,实现代码在自调用函数中自上而下执行的操作。
但是并不像promise、async一样,我们现在还没有办法在生产环境去使用它,但我们可以选择去了解它的存在,就像1999年有人尝试理解async一样,我们可以尝试虚构一段代码去实现剥离副作用的操作,试想当我们还在写回调地狱时,有人给你看了一眼async/generator,会不会很cool?
const featchList = (param1,param2) => { const num1 = fetchNum(param1); const num2 = fetchNum(param2); return num1 + num2 }接下来我们需要去了解什么是副作用? 如上,在调用featchList函数时,我们希望通过fetNum函数计算num1和num2的值,最后返回两数相加的结果。
我们看一下fetchNum函数内部是如何实现的。
const fetchNum = params => { let resp; new Promise( (resolve,reject) => { setTimeOut( ()=> { const resp = params; resolve(resp); }) }) return resp }如上,在fetchNum函数中我们模拟了一段异步的请求操作,最终两数相加的结果就是NaN(null+null=NaN),这 就是副作,也是我么为什么要剥离副作用的原因。
那javaScript现有语法是如何处理副作用操作的? 首先在不改变函数主体的情况下,我们考虑到的是使用await/yield,我们接下来去了解一下async/yield的实现
await/yield
const featchList = async (param1,param2) => { const num1 = await getNum(param1); const num2 = await getNum(param2); return num1+num2 }funciton *featchList() { const num1 = yield getNum(param1); const num2 = yield getNum(param2); return num1+num2 }如上,当程序执行碰到await/yield时,程序会从原有的函数执行流中跳到另外一个执行流中完成副作用(异步处理),并将副作用结果返回给当前执行流,从而达到程序在不同函数间跳跃的目的。
但是通过上述代码我们可以了解到,await/yield是具有传染性的,当调用函数featchList使用了async修饰符时,它的返回值变成了promise,类似的,如果featchList使用了generator修饰符,它的返回值就变成了Generator 。这意味着,调用featchList的函数外层也需要包裹一层async/*修饰符,这样看来await/yield并不具备普适性和通用性。
那我们可不可以在不改变函数主体的情况下,处理异步请求呢?
不可以,但接下来我们会模拟一段代码(try...handle)进行副作用剥离的操作,就像async未被纳入ECMA规范时,有人尝试理解async一样。
模拟代数效应
try.....catch
在认识try...handle之前,我们先回顾一下try ...catch 语法的异常处理机制。
const getNum = num => { if (!num) throw new Error('num is undefined'); return num; } const featchList = (param1,param2) => { try { const num1 = getNum(); const num2 = getNum(2); return num1+num2 } catch (err) { throw new Error('错误抛出:'+err.message) } }如上,经过万水千山之后,该代码最终抛出异常:“ 错误抛出:num is undefined”
我们在 fetchNum 里面 throw 一个 error:"num is undefined" , 但它冒泡到了离 featchList 执行代码最近的catch模块,这展示了try...catch一个重要属性:我们不需要手动将 error 一层层的向上传递,它会自动冒泡到离我们最近的 catch 模块。
try...hande正是借鉴了try...catch的异常处理机制,即我们并不需要关心中间层的错误处理,try...catch碰到异常后,throw这个异常会冒泡到最近的catch模块,try...handle需要副作用剥离时,我们是否可以throw一个effect冒泡到最近的handle模块?
try ... handle
上述
try...handle代码,我们虚构了两个语法
- perform
- resume with
perform 的作用类似于 throw,就像 throw 一个 error 进入catch模块一样,我们可以 perform 一个effect 进入handle模块
resume with 的作用就是,在handle模块处理完异步/同步的操作,拿到返回结果,我们可以通过resume with这个返回结果结果跳出handle模块,回到try中继续执行剩余代码
以const num1 = getNum(1)为例,我们 perform 整数1进入 handle模块,如果untilObj有整数1对应的策略,执行整数1对应的策略后,resume with 100 回到try中,将100赋值给num1 ,接下来执行 const num2 = getNum(2),最后return num1+num2
try...handle与try...catch的区别在于:try...catch一旦throw,后续代码不会执行;而try...handle不仅可以执行所有的代码直至结束,perform、resume with 更不需要区分同步或异步,保持函数逻辑的纯粹的同时,完成副作用从函数执行中剥离的功能。
hooks的践行
认识try...handle之后,我们知道try...handle通过perform、resume with处理异步操作 ,而Hooks也提供了两个主要api,useState与useEffect,它们之间有什么关联呢?
const List = ({id1,id2}) => { const num1 = useNum(id1) const num2 = useNum(id2) return num1+num2 }首先我们将 featchList函数 改为List,使它看起来更像一个function组件,{id1,id2}可以理解为对props的解构赋值。
接下来我们实现一个useNum(getNum改为useNum)
const useNum = id => { const [num, setNum] = useState(0); useEffect(() => { setTimeout(() => setNum(id), 0); }, [id]) return num }如上,useNum更相当于我们 try..handle 中的handle模块,通过useNum我们完成了对副作用的剥离,useEffect在这里面我们不需要关心同步或者异步的处理,只需要拿到结果后调用setNum函数,最后返回num
总结
React的目的是为了践行代数效应,当然这只是一次尝试,或许在不久的将来,我们就可以在es中原生支持这种方案
useFetch
最后贴上一段自己封装的代码,其目的是将请求服务端的一些操作从主文件剥离出去,使主要文件的代码更加易于阅读和理解,方便后期迭代
import React,{ useState,useEffect,useRef } from 'react' import useCallbackState from './useCallback.js' import { Modal,message } from 'antd' const useFetch = ( funC,options={}) =>{ const { successMsg, errMsg, callback, code } = options const [query,setQuery] = useState({}) const [data,setData] = useCallbackState({}) const [callBackParams,setCallParams] = useState({}) let ref = useRef(0) const fetchCallback = async ()=>{ try { console.log(query); const res = await funC(query) // 请求成功根据code判断,默认200 if(res.code=='200' || res.code==code){ successMsg && message.success(successMsg) callback && callback.call(null,res,callBackParams) setData(res) } } catch (error) { Modal.warning({ content: errMsg || error.message }) throw new Error(error.message) } } useEffect(() => { if (ref.current++) fetchCallback() // 初始化不请求 },[query]) return [data,function(params,options) { if(options) setCallParams(options); setQuery(params) }] } export default useFetch我们只需要在初始化进入页面时定义好请求方法
请求成功后也可以执行相应的回调函数
如果回调需要携带参数的话
多次异步请求我们可以这样去做