超性感的React Hooks(六)自定义hooks的思维方式

简介: 今天Spenser在公众号里说,今年许多公司都在裁员。市场上供大于求,但是,好多企业还是招不到人。真正的人才,市面上太稀缺了。这句话真的深有体会。我们公司想要招一个Java的高级开发,招了一个多月都没找到满意的。真的痛苦。出现这种局面,两极分化就会日渐严重。就以前端行业来看,厉害的人,会越来越难找,也会越来越值钱。而普通的人,混口饭吃都不容易。还是那句话说得对,外面的世界很精彩,可精彩是属于真正厉害的人

今天Spenser在公众号里说,今年许多公司都在裁员。市场上供大于求,但是,好多企业还是招不到人。真正的人才,市面上太稀缺了。这句话真的深有体会。我们公司想要招一个Java的高级开发,招了一个多月都没找到满意的。真的痛苦。


出现这种局面,两极分化就会日渐严重。就以前端行业来看,厉害的人,会越来越难找,也会越来越值钱。而普通的人,混口饭吃都不容易。


还是那句话说得对,外面的世界很精彩,可精彩是属于真正厉害的人


1


上一篇文章留下了一个思考题:在实践场景中,几乎每个页面都会在初始化时加载至少一个接口,而这个接口有一些统一的处理逻辑可以抽离,例如请求成功,返回数据,请求失败,异常处理,特定时机下刷新。我们如何利用自定义hooks来封装这个场景?


直接给出我的建议答案。


import {useState, useEffect} from 'react';
export default function useInitial<T, P>(
  api: (params: P) => Promise<T>,
  params: P,
  defaultData: T
) {
  const [loading, setLoading] = useState(true);
  const [response, setResponse] = useState(defaultData);
  const [errMsg, setErrmsg] = useState('');
  useEffect(() => {
    if (!loading) { return };
    getData();
  }, [loading]);
  function getData() {
    api(params).then(res => {
      setResponse(res);
    }).catch(e => {
      setErrmsg(errMsg);
    }).finally(() => {
      setLoading(false);
    })
  }
  return {
    loading,
    setLoading,
    response,
    errMsg
  }
}


在页面中使用


export default function FunctionDemo() {
    // 只需要传入api, 对应的参数与返回结果的初始默认值即可
    const {loading, setLoading, response, errMsg} = useInitial(api, {id: 10}, {});
}


当我们想要刷新页面,只需要执行一句代码即可


setLoading(true);


该方案仅供参考,具体使用需要根据实际情况优化,切勿直接生搬硬套。


实践场景也不会如此简单,会有更多的不同情况需要处理,例如,传入参数改变之后,需要重新请求接口刷新数据,我们应该怎么办?


这个留给大家自己结合项目情况去完善。


在这里更重要的是,我们应该想明白,自定义hooks解决了什么样的问题?


2


一些理解不够深刻的文章里说,解决了代码复用的问题?这样说准确吗?其实不完全准确。


准确来说,应该是逻辑片段复用


和组件化思维不同,这是另外一个粒度更细的代码复用思维。例如我们之前提到的,获取同样的数据。在组件化思维中,一个完整的组件,包括了这份数据,以及这份数据在页面上的展示结果。因此这是不同的复用思维。


处理获取数据过程中的公用逻辑,处理公用的登陆逻辑等。自定义hooks封装的大多数情况下不是一个完整的页面逻辑实现,而是其中的一个片段。


而和普通函数更强一点的是,自定义hooks还能够封装异步逻辑片段。


针对逻辑片段的封装,在React发展历史中的不同阶段,有不同的处理方案。面试的时候,许多面试官比较喜欢问这方面的问题。我们总结一下,各个阶段,分别都是如何处理的。


3


在medium上有一篇安利Render Prop[1]的文章非常火,到目前为止,已经有2万多的点赞。


微信图片_20220509212142.jpg


这篇文章中提到一个需求:某一个组件,需要跟踪鼠标的实时位置。例如拖拽,K线图,走马灯等场景都会需要用到这个逻辑片段。


Hooks


首先使用hooks,我们应该如何实现与运用?


自定义一个hook,这个钩子函数中保存位置信息,以及定义一个回调


import { useState } from ‘react';
export default function usePointor() {
  const [position, setPosition] = useState({x: 0, y: 0});
  function handleMouseMove(event: React.MouseEvent<HTMLDivElement, any>) {
    setPosition({x: event.clientX, y: event.clientY});
  }
  return {position, handleMouseMove}
}


使用时


import React from ‘react’;
import usePointor from './usePointor';
export default function MousePos() {
  const {position, handleMouseMove} = usePointor();
  return (
    <div onMouseMove={handleMouseMove} style={{width: 500, height: 500}}>
      <div>x: {position.x}, y: {position.y}</div>
    </div>
  );
}


Mixin


React最初使用React.createClass来创建组件。那个时候我们使用mixin来解决同样的问题。


首先创建一个mixin


const MouseMixin = { 
  getInitialState() { 
    return { x: 0, y: 0 }
  }, 
  handleMouseMove(event) { 
    this.setState({ x: event.clientX, y: event.clientY }) 
  }
}


然后在组件中使用


const App = React.createClass({
  mixins: [MouseMixin],
  render() {
    const { x, y } = this.state
    return (
      <div onMouseMove={this.handleMouseMove}>
        <div>x: {position.x}, y: {position.y}</div>
      </div>
    )
  }
})


对比两种方式,有非常大的共同之处。但是mixins的问题在于,我们不知道当前我使用的state属性来自于哪里。


当使用多个mixin时,如果各自定义的state命名冲突了怎么办?


mixin当年非常受欢迎,但这两个问题一直是mixin的痛点,导致我们在自定义mixin时必须非常小心。特别是在大型多人协作的项目中,常常会因为维护不好带来麻烦。而这样的痛点,在hooks中不存在。


class


class语法取代React.createClass来创建组件之后,我们使用高阶组件的方式来达到同样的目的。


首先定义一个高阶组件


const withMouse = (Component) => {
  return class extends React.Component {
    state = { x: 0, y: 0 }
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }
    render() {
      return (
        <div onMouseMove={this.handleMouseMove}>
          <Component {...this.props} {...this.state} />
        </div>
      )
    }
  }
}


然后使用


const App2 = withMouse(({ x, y }) => {
  return (
    <div style={{ height: '100%' }}>
      <div>x: {position.x}, y: {position.y}</div>
    </div>
  )
})


高阶组件面临与mixin同样的问题,当嵌套使用多个高阶组件时,我们在代码中无法识别props中的参数,是哪里来的。并且当参数命名重复时一样无法解决。因此高阶组件在使用时我们也会非常小心,以至于在很多场景下,我们放弃共同逻辑片段的封装,因为这会很容易造成滥用。


render props


class组件中,复用逻辑片段,最好的方案是render props。


首先创建一个组件


mport React from ‘react’;
interface Props {
  render: (props: {x: number, y: number}) => any
}
export default class Mouse extends React.Component<Props> {
  state = { x: 0, y: 0 }
  handleMouseMove = (event: any) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }
  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    )
  }
}


然后使用


import React from 'react';
import Mouse from ‘./Mouse’;
export default function MousePos() {
  return (
    <div>
      <Mouse 
        render={({ x, y }) => (
          <div>x: {x}, y: {y}</div>
        )}
      />
    </div>
  )
}


render props使用非常广泛。原因就在于render props解决了来源问题,同时也避免了命名冲突。


但render props的问题在于


1.可读性不高,直观上比较别扭。我们可以在Mouse组件中处理很多额外逻辑,甚至定义更多的交互样式。因此使用时会造成一些困扰。2.存在局限性。我们期望的是能够切割逻辑片段,render props最终仍然是组件化思维的扩展运用3.代码不够优雅,这个是我个人主观上的看法


自定义hook是目前为止,解决逻辑片段复用的最佳方案。


这种思维和函数式编程思维有共通之处。


例如数组的forEach, map, filter等方法。在所有的for循环中,共同的逻辑是对每一个元素的遍历。我们可以将这个逻辑抽取出来。封装成为forEach


forEach:循环过程中,执行某种操作


Array.prototype.forEach = function () {
  const ary = this;
  const [callbackfn, thisArg] = [].slice.call(arguments);
  if (typeof callbackfn !== 'function') {
    throw new TypeError(callbackfn + 'is not a function')
  }
  for (let i = 0; i < ary.length; i++) {
    callbackfn.call(thisArg, ary[i], i, ary);
  }
}


map 循环过程中,返回新的子项,最终组成新的数组。


和forEach相比,循环过程中执行的某种操作具体化了,map完全可以基于forEach实现。


function map(arr, fn) {
  const res = [];
  arr.forEash((item, i) => {
    res[i] = fn(item, i);
  })
  return res;
}


filter: 循环过程中,过滤出符合条件的子项。同理。


function filter(arr, fn) {
  const res = [];
  arr.forEash((item, i) => {
    const isOk = fn(item, i);
    if (isOk) {
      res.push[item];
    }
  })
  return res;
}


同样的方式,我们可以非常简单的基于forEach实现some, every,reduce等方法。


原则上来说,公共逻辑片段无论是在业务场景中,还是在工具模块中,都非常多。而React Hooks能够轻松解决在React环境中的逻辑片段封装。这是自定义hook的底层思维。


理解了这个思维,我们能够容易的辨别出来,哪些场景需要使用自定义hooks。也能够感受得到,在大型项目中,自定义hooks对于大型项目的重要性。


React Hooks剩余的许多api,包括useCallback,useMemo等,其实都是自定义的hooks,利用本文提到的公共片段思维,很快就能掌握他们。

相关文章
|
4天前
|
设计模式 存储 前端开发
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
|
1月前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
6天前
|
前端开发 JavaScript
|
14天前
|
前端开发 开发者
彻底颠覆!React Hooks带来前端开发的革命,你准备好了吗?
【8月更文挑战第6天】在现代Web开发中,React作为顶级前端框架,以高效性能和丰富生态著称。React Hooks自16.8版本引入,赋予函数组件使用状态和生命周期的能力,使代码更简洁、模块化,易于维护。常用Hooks如`useState`、`useEffect`等简化了状态管理和副作用操作。Hooks不仅增强了组件能力,提高了代码可读性和可维护性,还在各种应用场景中展现出强大功能,尤其是在中大型项目中优化了代码结构和提升了开发效率。总之,React Hooks为前端开发注入了新活力,推动了更高效便捷的开发实践。
26 1
|
1月前
|
前端开发
React Hooks实战技巧:提升你的组件开发效率
【7月更文挑战第16天】React Hooks为React开发带来了革命性的变化,使得函数式组件更加强大和灵活。通过掌握上述实战技巧,你可以更高效地编写清晰、可维护和可复用的React组件。希望这些技巧能帮助你在React开发之路上走得更远。
|
10天前
|
JavaScript 前端开发 API
浅谈:为啥 Vue 和 React 都选择了 Hooks?
浅谈:为啥 Vue 和 React 都选择了 Hooks?
|
1月前
|
存储 前端开发 JavaScript
react hooks 学习进阶
【7月更文挑战第12天】 React Hooks(自16.8版起)让函数组件能处理状态和副作用。useState用于添加状态管理,useEffect处理副作用,useContext共享数据,useReducer处理复杂状态逻辑,useRef获取引用。进阶技巧涉及性能优化,如useMemo和useCallback,以及遵循规则避免在不适当位置调用Hooks。理解异步更新机制和结合Redux等库提升应用复杂性管理。持续学习新技巧是关键。
30 0
|
缓存 前端开发 API
react hooks详解
分析react开发过程中,经常用到的hook方法
204 0
|
3月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
264 0
|
3月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
90 0