最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 这一次请记住React Hook

    正文概述 掘金(滴滴出行_用户体验)   2021-01-20   441

    一般介绍一个新的东西(知识),分三个部分:是什么?为什么?怎么用?

    React Hook是什么?

    官方解释:Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

    翻译一下:从现在开始,react函数式组件不再是无状态组件,你可以通过hook直接让函数式组件拥有class组件的能力。

    为什么要用React Hook?

    看官方的意思:不是替代class组件吧,也至少是想引导用户把函数式的组件写起来。那么为什么要往这个方面引导呢?

    class组件有什么问题?

    • 随时间推移,组件臃肿难以维护和复用
    • 声明周期里的函数比较乱
    • class的特殊书写形式(this绑定)
    • class编译会变大,性能不好,热加载不稳定
    • class自身复杂度高

    其中复用是最棘手的问题,也是hook出现的最根本的原因。

    没有hook的时候,我们用什么办法实现class组件的复用?

    • 属性渲染
    • 高阶组件

    这两种方式都受限于class组件的本身问题,也不能根本解决提升组件复用性的问题,只是提供了复用的手段。

    函数式组件结合hook的方式,让组件可以有自己的状态,并且可以高复用,页面里class组件减少也会更好维护。

    怎么就体现了好的复用性呢?具体的优势有哪些?

    • 函数组件没有生命周期
    • 不存在特殊的书写方式
    • hook的注入让其拥有class组件的能力,但同时又有简洁的书写方式
    • 复用的本质是那些封装起来的hook能力,例如自定义的hook,那是一部分独立的功能组件

    仔细回味一下,在实际开发中是不是碰到过不得已将函数式组件更改为class组件,从而进入到了难为维护和复用的世界里。

    怎么使用React Hook?

    常规用法

    先看一个例子,就不从一个简单的使用api开始了,直接看封装Hook的玩法

    const useUserList = () => {
        const [loading, setLoading] = useState(false);
      const [users, setUsers] = useState([]);
      const loadUsers = async params => {
        setLoading(true);
        setUsers([]);
        const users = await loadUsers('/request', params);
        setUsers(users);
        setLoading(false);
      };
      const addUsers = useCallback(
        user => setUsers(users => users.concat(user)),
        []
      );
      const deleteUsers = useCallback(
        use => setUsers(users => without(users, user)),
        []
      );
      return [users, {loading, loadUsers, addUsers, deleteUsers}];
    }
    

    上面是一段简单的封装hook操作,业务逻辑中经常会使用到。

    先体会一下这个hook的功能:

    • 提供用户列表
    • 提供加载、添加和删除操作
    • 还有加载状态,这可能影响页面的行为

    这是在干什么?管理状态和关联的行为,是状态和行为的封装,这是复用的本质,未来只要用到用户方面的数据和操作复用这个hook好了。所以看到这里,复用的不是UI,而是能力。

    如何合理使用

    当然上面的书写方式对我来说还没能感受到“真香”的现场,只是换了一个方式抽离逻辑的书写,那么试着分析看看上面的例子,在我看来,更加合理的使用是让hook发挥其更强的复用性价值,如果做到这一点那么Hook才是最有意义的。看看下面的硬核现场分析。

    从上面这个例子看,封装是状态和关联的行为的打包,那么可以理解hook就是“给我一些变量和方法,我给你封装一个hook”。从最底层通用的角度翻译成代码如下:

    export const useMethods = (initialValue, methods) => {
      const [value, setValue] = useState(initialValue);
      const boundMethods = useMemo(
        () => Object.entries(methods).reduce(
            (methods, [name, fn]) => {
            const method = (...args) => {
              setValue(value => fn(value, ...args));
            };
            methods[name] = method;
            return methods;
          },
          {}
        ),
        [methods]
      )
      return [value, boundMethods];
    }
    

    上述只是最底层的hook封装办法,想要解决业务逻辑还需要其他通用Hook来调用。

    既然Hook是解决状态和关联行为的复用能力,那么从状态和行为两个方面下手:状态是变量,变量是简单的数据结构,而行为其实是过程,是一些更为通用Hook和方法的组合。

    拿例子里用到的User数组类型来对数据结构进行封装,看代码

    const arrayMethods = {
        push(list, item) {
        return list.concat(item)
      },
      pop(list) {
        return list.slice(0, -1);
      },
      slice(list, start, end) {
        return list.slice(start, end);
      }
      ...
    }
    export const useArray = (initialValue = []) => {
        assert(Array.isArray(initialValue), 'initialValue must be an array');
      return useMethods(initialValue, arrayMethods);
    }
    

    再来看看行为的封装,拿上面的例子的请求数据进行封装

    const useTaskPending = task => {
      const [pending, setPending] = useState(false);
      const taskWithPending = useCallback(
        async (...args) => {
            setPending(true);
          const result = await task(...args);
          setPending(false);
          return result;
        }, 
        [task, setPending]
      );
      
      return [taskWithPending, pending];
    }
    

    除了过程的封装,还要针对结果进行状态同步管理

    const useTaskPendingState = (task, state) => {
        const [taskWithPending, pending] = useTaskPending(task);
      const callAndStore = useCallback(
        async () => {
          const result = await taskWithPending();
          state(result);
        },
        [taskWithPending, state]
      )
      return [callAndStore, pending];
    }
    

    现在我们将上述例子里的hook过程经过数据结构和过程封装以后,代码修改成如下的结果:

    const useUserList = () => {
        const [users, {push, pop, slice}] = useArray([]);
      const [load, pending] = useTaskPendingState(getListUser, state);
      
      return [users, {pending, load, addUser: push}]
    }
    

    再看看上述的表达,极简到震惊脸。而且useArray和useTaskPendingState可以重复被使用,越是通用底层的Hook,越是复用性强。

    品到这里,可以深刻感受到hook的“香”在哪里,只要你理解到Hook解决的问题是什么?本质是复用什么?针对这个复用我们可以极致到什么程度?

    其实目前业务代码里不太可能按照上述分析的方式进行书写,但是可以通过分析感受到Hook的使用原则,以及面对工作里业务逻辑怎么思考Hook的使用和封装。现阶段,能够写出第一个例子的情况已经很优秀了。

    Hook的实现原理

    hook的使用上面有两个原则

    • 不要再循环、条件判断和嵌套函数里使用
    • 只在react的函数式组件里使用

    原理部分拿useState举例:状态管理都是关于数组

    先看useState怎么工作的?

    function renderFunction() {
        const [firstName, setFirstName] = useState('qingming');
      const [lastName, setLastName] = useState('dong');
      
      return (
        <Button onClick={() => setFirstName('damu')}>SET</Button>
      )
    }
    

    代码逻辑不解释,那么React怎么去工作的呢?注意上面的黄色文字,模拟实现步骤:

    • 创建两个空数组,state一个,setter一个,一个下标变量cursor
    • 第一次渲染
      • cursor = 0 state = ['first'] setters = [setFirstName]
      • cursor = 1 state = ['first', 'last'] setters = [setFirstName, setLastName]
    • 第二次渲染(第N次渲染)每一次重新渲染cursor都会重置为0
      • cursor = 0 state = ['first'] setters = [setFirstName]
      • cursor = 1 state = ['first', 'last'] setters = [setFirstName, setLastName]
    • 事件处理
      • setFirstName('damu')
      • 找到setter里对应的方法,记录当前的cursor
      • 找到state里对应cursor的状态值
      • 替换state里对应位置的值

    useState底层实现大概可以如下:

    let state = []
    let setters = []
    let cursor = 0
    let firstRun = true
    // 实现cursor与setter关联,调用方法直接找到cursor对象的state进行修改
    function createSetter(cursor) {
        return function setterWithCursor(newVal) {
        state[cursor] = newVal
      }
    }
    export function useState(initValue){
      // 初次渲染进行数组的更新,其他情况下不需要操作数组
        if (firstRun) {
        state.push(initValue)
        setters.push(createSetter(cursor))
        firstRun = false
      }
      
      const value = state(cursor)
      const setter = setters(cursor)
      
      cursor++
      return [value, setter];
    }
    

    最后解释一下hook为什么不能再循环、条件语句和函数嵌套里使用。拿条件语句来看

    let firstRender = true;
    function RenderFunctionComponent() {
      let initName;
      
      if(firstRender){
        [initName] = useState("qingming");
        firstRender = false;
      }
      const [firstName, setFirstName] = useState(initName);
      const [lastName, setLastName] = useState("dong");
      return (
        <Button onClick={() => setFirstName("damu")}>Fred</Button>
      );
    }
    

    将上面的代码逻辑,使用useState底层实现来翻译一遍是这样的:

    • 第一次执行
      • cursor = 0 state = ['first'] setters = [setter_0]
      • cursor = 1 state = ['first', 'first'] setters = [setter_0, setFirstName]
      • cursor = 2 state = ['first', 'first', 'last'] setters = [setter_0, setFirstName, setLastName]
    • 第二次执行,不再执行条件语句,直接执行两次初始化
      • cursor = 0 state = ['first', 'first', 'last'] setters = [setter_0, setFirstName, setLastName]
      • cursor = 1 state = ['first', 'first', 'last'] setters = [setter_0, setFirstName, setLastName]

    此时我们的firstName = lastName = 'first',而我们的代码意思确实firstName = 'first' !== lastName = 'last'。

    由此可见,数组管理的严谨性,为什么不灵活管理这些下标呢?可能有性能问题吗?看未来会不会变化? 相信梳理到这里,针对hook你应该理解了,更多Hook api和实现底层快去实践和学习吧。


    起源地下载网 » 这一次请记住React Hook

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元