[从零实践03-后台] 自定义hooks

导航

[react] Hooks

[从零实践01-后台] 代码分割
[从零实践02-后台] 权限控制
[从零实践03-后台] 自定义hooks

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend









[源码-vue06] Vue.nextTick 和 vm.$nextTick

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数


















前置知识

(1) 一些单词

phenomenon:现象

converter:转换器
(比如:对原始数据的转换,过滤等)

memory:记忆
(比如:函数式编程中的 记忆函数 )

deprecated:已弃用
( vscode-tslint (deprecated) )

mutable:可变的
( interface MutableRefObject<T> { current: T;} )

associate:关联
(
  // 表关联的字段
  User.associate = function() {
    // 一对多
    app.model.User.hasMany(app.model.Diary, { foreignKey: 'user_id', targetKey: 'id'})
  }
)

legend:图例

imperative:命令式,强制,迫切
( useImperativeHandle )

(2) useEffect

  • function useEffect(effect: EffectCallback, deps?: DependencyList): void;
  • useEffecth没什么好说的,主要说说 ( 依赖数组 )
    • 当 ( 依赖数组 ) 是 ( 引用类型 ) 时
      • 自定义引用类型
        • 函数或者对象: useMemo(() => 一切值,但引用类型才有必要缓存, []) 做缓存,因为依赖通过 Object.is()比较永远false,useEffect就会重新执行,死循环
        • 如果是函数:useCallback(fn, [])做缓存
        • 其实:useMemo(() => fn, []) 等价于 useCallback(fn, [])
      • 在state中的引用类型
        • 不用做缓存,react会自动帮我们完成
        • 具体可以看后面的 useFetch 中的例子

(3) 神奇的 useRef

  • const refContainer = useRef(initialValue);
    • 函数签名:function useRef<T>(initialValue: T|null): RefObject<T>;
    • 如果报错current是常量的话,需要这样传入类型变量 const countRef = useRef<类型|null>(null)
  • 相关方法:
    • useImperativeHandle
      • useImperativeHandle(ref, createHandle, [deps]) 可以让你在使用 ref 时,自定义暴露给父组件的实例值
      • useImperative应该与React.forwardRef一起使用,并尽量避免这样的命令式操作
    • React.forwardRef
      • React.forwardRef(fnComponent(props, ref)) 可以接收父组件传递过来的 ref 对象,并且返回一个react节点
  • useRef返回一个 ( 可变的ref对象 ),( ref.current ) 被初始化为 initialValue
  • 返回的 ref 对象在整个组件生命周期内 ( 保持不变 ),即每次渲染返回的都是同一个ref对象
  • 注意点
    • useRef返回的ref对象,在组件整个生命周期保持不变,即相当于class组件中的实例属性
    • ref对象的值更新后,不会重新渲染
    • ref.current发生变化应该作为Side Effect(因为它会影响下次渲染),所以不应该在render阶段更新current属性
    • ref.current 不可以作为其他 hooks(useMemo, useCallback, useEffect)依赖项
    • useState每次返回的state都是快照,每次渲染相互独立;而useRef不是快照,而是同一个对象的引用
  • 使用场景
    • 1. 绑定DOM
    • 2. 父组件调用子组件中的方法
    • 3. 保存任意可变值,类似于class中的实例属性
    • 4. 利用 ( useRef+setTimeout ) 可以实现修改state后,立即获取修改后的值 - 注意的是如果立即修改state后,只利用setTimeout去获取state,也不是最新的,因为获取的是state的快照,即使延时打印
  • useRef,useImperativeHandle,React.forwardRef综合案例:
import React, { useRef, useState, useEffect, useImperativeHandle } from 'react'

interface IGetmessage {
  getMessage: () => void
}

const Father = () => {
  const [count, setCount] = useState(0)
  const inputRef = useRef<HTMLInputElement>(null)
  const childRef = useRef<IGetmessage>(null)
  const countRef = useRef<number | null>(null)

  useEffect(() => {
    countRef.current = count
    // 每次渲染后,都更新ref.current
  })

  const getFocus = () => {
    inputRef.current && inputRef.current.focus()
    console.log('useRef绑定DOM节点');
  }

  const add = () => {
    setCount(prevCount => prevCount + 1)
    console.log(count, 'setState后,立即打印state的值,是上一次的state值,因为此时回调并没有执行,如果要拿到的话,可以使用setTimeout宏任务+sueRef实现,在更新后打印')
    setTimeout(() => {
      console.log(countRef.current, '这是在setState后利用 ( setTimeout+useRef ) 获取的最新的state的值')
    }, 1000)
    setTimeout(() => {
      console.log(count, '注意:如果直接在setState后,利用setTimeout中打印state,任然不是更新后的state,因为state保存的是快照')
    }, 1000)
  }
  const delayConsole = () => {
    setTimeout(() => {
      console.log('不用useRef延时打印cout,是打印的快照count,即打印的是当时add得到的count,而不是现在add得到的count :>> ', count);
    }, 3000)
  }


  const delayConsoleUseRef = () => {
    setTimeout(() => {
      console.log('用useRef保存count的值,每次渲染后更新ref.current,延时打印的不是快照,而是本次渲染的count :>> ', countRef.current);
    }, 3000)
  }

  const getChildMehod = () => {
    childRef.current && childRef.current.getMessage() // 调用子组件传递的方法
  }

  return (
    <div style={{ background: '#fff', margin: '10px 0', padding: '10px', border: '1px solid black' }}>
      <p>父组件</p>
      <p style={{
        margin: '10px', padding: '14px 24px', background: '#e8eaff',
        border: '1px solid #345bf9', display: 'inline-block',
      }}> useRef </p>

      <div>
        <input type="text" ref={inputRef} />
        <button onClick={getFocus}>获取焦点</button>
      </div>

      <br />

      <div style={{ background: '#bcffb7', padding: '10px', margin: '10px 0' }}>
        <p>count: {count}</p>
        <button onClick={add}>add</button> &nbsp;
        <button onClick={delayConsole}>不用useRef时,延时打印count</button> &nbsp;
        <button onClick={delayConsoleUseRef}>用useRef保存count的值,延时打印count</button>
      </div>

      <br />
      <button onClick={getChildMehod}>useRef+useImperativeHandle实现父组件调用子组件的方法</button>
      <Child ref={childRef} />
    </div>
  )
}

const Child = React.forwardRef((props: any, ref: any) => { // react.ForwardRef() 获取父组件传递的ref作为子组件props
  useImperativeHandle(ref, () => ({ // useImperativeHandle() 设置允许子组件暴露给父组件的方法
    getMessage: () => {
      console.log('打印子组件方法的内容');
    }
  }))

  return (
    <div style={{ margin: '10px', border: '1px solid red', padding: '4px' }}>
      <p>子组件</p>
    </div>
  )
})

export default Father

(4) useReducer,useContext,React.createContext 实现一个简单的 redux

  • reducer函数
    • 概念:
      • (state, action) => newState
    • 特点:
      • 必须是一个纯函数
      • Array.prototype.reduce(reducerFunction)
        • 回调函数reducerFunction就是一个reducer函数,接受一个旧的结果值,返回新的结果值并赋值给旧的结果值,然后继续迭代
  • useReducer
    • const [state, dispatch] = useReducer(reducer, initialArg, init);
  • useContext
    • const value = useContext(context) 参数是context实例,返回值是context当前的值,即最近的Provider组件的value值
    • useContext(context) 相当于 context.Consumer
    • useContext(context) 的作用是:读取context 的值,和 订阅context的变化在上层还是需要 context.Provider提供context的值
  • React.createContext()
    • const MyContext = React.createContext(defaultValue) 生成一个context实例
    • context.Provider
    • context.Consumer
      • <MyContext.Consumer>{value => React节点元素}</MyContext.Consumer>
      • value是最近的Provider提供的value,如果不存在Provider,则使用React.createContext(defaultValue)的value值
  • 实现一个redux
import React, { useReducer, useContext } from 'react';
import ReactDOM from 'react-dom';

// Store => 用React.createContext()生成一个context实例
const Store = React.createContext()

// initialState => 初始化reducer的state
const initialState = {
  count: 0
}

// reducer函数 => (state, action) => newState
const reducer = (state, action) => {
  switch (action.type) { 
    case 'ADD':
      return { ...state, count: action.payload }
    default:
      return { ...state }
  }
}

// Provider组件 => 提供 context 给子组件消费
const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState) // useReducer
  return <Store.Provider value={{ state, dispatch }}>
    {children}
  </Store.Provider>
}

// App组件
const App = () => {
  const { state, dispatch } = useContext(Store)
  const { count } = state
  return <> 
          <p> count: {count}</p>
          <button onClick={() => dispatch({ type: 'ADD', payload: count + 1 })}>add</button>
        </>
}

// 挂载
ReactDOM.render(
  <Provider>
    <App />
  </Provider>,
  document.getElementById('root')
);

(5) useCallback,useMemo,React.Memo 做性能优化

  • useCallback
    • useCallback(fn, deps)
      • fn:需要缓存的函数
      • deps:依赖数组
    • 作用
      • 对函数做缓存,在依赖项没变化时,直接返回缓存的函数,类似函数记忆,只有当依赖项变化时才从新返会新的函数
    • 使用场景
      • 1. 用于父组件向子组件传递函数属性时,来缓存传递的函数,然后子组件通过 ( React.memo ) 对props做浅比较,如果useCallback的依赖项不变,那么传递的函数属性会直接复用,浅比较的函数属性就不会变化,从而可以用来做性能优化
      • 2. 用于 useEffect() 的依赖做对比变化时,对函数做缓存
  • useMemo
    • 作用:
      • 主要用于对 ( 对象类型 ) 的数据进行缓存,淡然任意的类型的值都可以,只是对象用于优化
      • 也能实现 useCallback 的功useMemo是useCallback的超集,可以缓存任意类型的数据
      • useCallback(f1, [a]) 和 useMemo(() => f1, [a]) 相等的,缓存函数
      • useMemo(() => object, [a])
    • 使用场景
  • React.memo
    • React.memo(functionComponent, areEqual)
      • 第一个参数:functionComponent需要缓存的组件;
      • 第二个参数:areEqual缓存的条件函数,返回true就缓存,返回false就重新渲染
    • React.memo() 用于函数式组件,默认只是对props做浅比较,如果要做进一步的比较使用第二个参数函数
React.memo(functionComponent, areEqual) 
1. 第一个参数是需要缓存的functionComponet
2. 第二个参数是具体比较规则函数areEqual,返回ture不重新渲染,返回false才会重新渲染
3. React.memo用于函数式组件,默认只是对props做浅比较,如果要做进一步的比较使用第二个参数函数
React.memo优化的例子


// 父组件
const Father = () => {
  const [count1, setCount1] = useState({ number: 1 })
  const [count2, setCount2] = useState(2)
  const addCount1 = () => {
    setCount1(prev => ({ ...prev, number: prev.number + 1 }))
  }
  const addCount2 = () => {
    setCount2(prev => prev + 1)
  }
  return <MemoryChild count1={count1} addCount1={addCount1} count2={count2} addCount2={addCount2} />
}

// 子组件
const Child = ({ count1, count2, addCount1, addCount2 }) => {
  return (
    <>
      <p>count1: {count1.number}</p>
      <p>count2: {count2}</p>
      <button onClick={addCount1}>add - count1</button>
      <button onClick={addCount2}>add - count2</button>
    </>
  )
}
const areEqual = (prev, next) => {
  // 1. 当count2变化时,Child是不重新渲染的
  // 2. 只有当count.number变化时,才会重新渲染
  return prev.count1.number === next.count1.number
}
const MemoryChild = React.memo(Child, areEqual) // ------------- React.memo(functionComponent, areEqual)
  • useCallback,useMemo,React.Memo 做性能优化
const Father = () => {
  const [count, setCount] = useState(0)
  const [number, setNumber] = useState(1)

  const add = () => {
    setCount(prevCount => prevCount + 1)
  }
  const memoryAdd = useCallback(add, [])

  const obj = { age: 20 }
  const memoryObj = useMemo(() => obj, [])

  return (
    <>
      <button onClick={() => setNumber(number => number + 1)}>点击 - 改变number</button>
      <Child count={count}>用callback</Child>
      <NotMemoryFnChild count={count} add={add} />
      <MemoryFnChild count={count} memoryAdd={memoryAdd} />
      <NotMemoryObjChild obj={obj} />
      <MemoryObjChild memoryObj={memoryObj} />
    </>
  )
}

const Child = () => {
  return (
    <div style={{ margin: '10px', border: '1px solid red', padding: '4px' }}>
      <div>纯函数组件 - 父组件重新渲染,该子组件就会重新渲染</div>
      <div>{Math.random()}</div>
    </div>
  )
}

const NotMemoryFnChild = React.memo(({ count, memoryAdd }) => {
  return (
    <div style={{ margin: '10px', border: '1px solid red', padding: '10px' }}>
      <div>不用 ( useCallback ) 只用React.memo做对props浅比较,props中有函数时,每次钱比较的结果都是变化,子组件会重新渲染</div>
      <div>{Math.random()}</div>
    </div>
  )
})

const MemoryFnChild = React.memo(({ count, add }) => {
  return (
    <div style={{ margin: '10px', border: '1px solid red', padding: '10px', background: 'yellow' }}>
      <div>用 useCallback() 缓存子组件的props中的函数,并用React.memo做浅比较,props没变,子组件不重新渲染</div>
      <div>{Math.random()}</div>
    </div>
  )
})

const NotMemoryObjChild = React.memo(({ obj }) => {
  return (
    <div style={{ margin: '10px', border: '1px solid red', padding: '10px' }}>
      <div>不用 useMemo() 缓存 props中的对象属性,即使在React.Memo() 做浅比较,因为有对象props,每次都是一个新对象,导致浅比较的结果是props变化,子组件更新</div>
      <div>{Math.random()}</div>
    </div>
  )
})

const MemoryObjChild = React.memo(({ memoryObj }) => {
  console.log(memoryObj, 'memoryObj');
  return (
    <div style={{ margin: '10px', border: '1px solid red', padding: '10px', background: 'yellow' }}>
      <div>用 useMemo() 缓存 props中的对象属性,在React.Memo() 做浅比较,因为对象props做了缓存,props做浅比较时没有变化,子组件不更新</div>https://juejin.im/editor/drafts/6882614048181993479
      <div>{Math.random()}</div>
    </div>
  )
})

(6) 链判断运算符 ?.

  • ?.在链式调用的时候判断,左侧的对象是否为null或undefined,是的,就不再往下运算,而是返回undefined
  • 链判断运算符有三种用法
    • obj?.attribute 对象属性
    • obj?[attribute] 对象属性
    • function?.(params) 函数/方法的调用
const b = {fn: x}
function x(params) {console.log(params) }


b.fn?.(1111111)
// b.fn存在,就调用fn(1111111)
// b.fn不存在,返回 undefined
// ---------- 链判断运算符,判断左边对象是否为null或undefined,是返回undefined,不是就继续往下判断

(7) === 和 Object.is 的区别

  • ===
    • ( 数据类型 ) 和 ( ) 都一样,才严格===相等
    • 对象仅与自身严格相等 即使两个对象属性完全一样,也不相等
  • Object.is
    • 和严格相等运算符相似
  • 问题
    • 在useEffect等hooks的依赖项对比时,使用的是Object.is(),所以当依赖是对象(对象,数组,函数等)类型时,总是不相等,失去意义
  • 解决方案:
    • 如果是函数,可以用 useCallback 做缓存,这样在useEffect的依赖项有函数时,保证每次函数固定不变
=== 和 object.is 区别如下


NaN === NaN // false
Object.is(NaN, NaN) // true

+0 === -0 // true
Object.is(+0, -0) // false

(8) 数组的 - 非数字键

  • for in 可以遍历数组的 ( 数字键 ) 和 ( 非数字键 )
  • 所以:不建议用 for in 循环遍历数组,因为会遍历到非数字键,其他的比如 for, forEach 都只会遍历到数字键
  • for in 可以遍历数组和对象
const arr = [1, 2, 3]
arr.four = 4
arr.five = 5
// arr =>  [1, 2, 3, four: 4, five: 5]


for(let i in arr) { 
  console.log(i) 
}
// 1 2 3 four five

(9) 伪类 :nth-child

选中前5个 ------------------------------ :nth-child(-n+5)
选中第5个元素开始的后面的孩子 ----------- :nth-child(n+5)
选中第 5-10 个孩子 ---------------------- :nth-child(n+5):nth-child(-n+9)

(10) echarts在react-hooks中的封装

  • 封装要实现的功能

    • 传入参数:
      • options: object echarts数据配置对象
      • isResize: boolean 是否自适应窗口的变化
      • showLoading: boolean 是否显示加载数据动画
      • events: object 事件对象,可以传出需要监听的事件
        • key:是事件名称
        • value:是回调函数,回调参数是events对象echarts实例
      • wrapStyle: object 传入的样式
      • className:string 可以具体传入class名
  • 需要用到的 echarts 的相关属性和事件

    • echarts
      • echarts.init(dom, theme, opts)
      • echarts.getInstanceByDom(dom)
    • echartsInstance :echarts.init()生成的实例
      • echartsInstance.setOption():echarts图标的数据项
      • echartsInstance.on:绑定事件的处理函数
      • echartsInstance.resize():改变图表尺寸,在容器大小发生改变时需要手动调用
      • echartsInstance.showLoading():显示加载动画
      • echartsInstance.hideLoading():关闭加载动画
      • echartsInstance.clear(): 清空当前实例,会移除实例中所有的组件和图表
  • 代码

封装

import React, { useEffect, useRef, useState } from 'react'
import echarts from 'echarts'
interface Ioption {
  option: IAny; // 配置对象
  wrapStyle?: IAny; // 样式
  className?: string; // 自定义class,为了不影响全局,最好加上唯一的前缀
  theme?: string; // 主题
  events?: IAny; // 事件的配置对象,key事件名,value事件的回调,回调有events和echarts实例两个参数
  isResize?: boolean; // 是否自适应窗口变化
  showLoading?: boolean; // 是否显示loading
}
interface IAny {
  [propName: string]: any
}


const HocEcharts = ({
  option, // 配置对象
  wrapStyle = { width: '400px', height: '400px', background: '#fff' }, // 样式
  className,// 自定义class,为了不影响全局,最好加上唯一的前缀
  theme = 'vintage', // 主题
  showLoading = true, // 是否显示loading
  isResize = true, // 是否自适应窗口变化
  events, // 事件的配置对象,key事件名,value事件的回调,回调有events和echarts实例两个参数
}: Ioption) => {
  const ref = useRef<HTMLDivElement|any>(null)

  let instance: echarts.ECharts

  // getInstance 创建或获取实例
  const getInstance = async () => {
    instance = await echarts.getInstanceByDom(ref.current) || await echarts.init(ref.current, theme)
    instance.clear() // 清除实例
  }

  // setOption 设置配置项
  const setOption = async () => {
    showLoading && instance.showLoading('default') // loading动画开始
    await new Promise(resolve => {
      setTimeout(() => {
        instance && instance.setOption(option) // 模拟异步
        resolve()
      }, 1000)
    })
    showLoading && instance.hideLoading() // loading动画开始
  }

  const bindEvent = () => {
    if (instance && events) {
      for (let i in events) {
        instance.on(i, events[i].query, (e: any) => events[i].callback(e, instance))
      }
    }
  }

  const init = async () => {
    await getInstance() // 生成或者获取echart实例
    await setOption() // 设置echarts配置项
    await bindEvent() // 绑定事件
  }

  const resizeEcharts = () => {
    instance && instance.resize()
  }

  useEffect(() => {
    init()
  }, [])

  useEffect(() => { // 监听窗口变化,echarts自适应
    if (isResize) {
      window.addEventListener('resize', resizeEcharts)
      return () => window.removeEventListener('resize', resizeEcharts) // 移除监听
    }
  }, [])

  return (
    <div ref={ref} style={wrapStyle} className={className} />
  )
}

export default HocEcharts 
调用

<HocEcharts
  option={barOption2}
  className="custom-echarts-bar"
  theme={theme}
  isResize={true}
  showLoading={true}
  events={Events}
/>
(二) 自定义hooks

(1) usePrevious

  • 用来获取上一个值
import { useEffect, useRef } from 'react';

interface IusePrevious {
  <T>(state: T): T
}

export const usePrevious: IusePrevious = (state) => {
  const ref = useRef(state)
  useEffect(() => {
    ref.current = state
  })
  return ref.current
}

(2) useModal

  • const { CustomModal, toggle } = useModal(title)
  • 参数:传入modoal的title,content从children中获取
  • 返回值:返回一个CustomModal组件,和切换显示隐藏的回调
  • 定义
定义:
// 注意点:useModal和返回的CustomModal都接收了props


import React, { useState } from 'react'
import { Modal } from 'antd'

interface IformInstance {
  submit: () => void // form实例上的属性,这里只写了submit方法,其他业务逻辑type自行添加
}

interface ICustomModalProps {
  formInstance?: IformInstance;
  children: any; // 必传属性
}

const useModal = (title: string) => {
  const [visible, setVisible] = useState(false)
  const toggle = () => {
    setVisible(prevVisible => !prevVisible)
  }

  const CustomModal = (props: ICustomModalProps) => { // 返回CustomModal组件给业务方使用
    const {formInstance, children} = props
    const handleOk = () => {
      formInstance && formInstance.submit() // 如果child是form实例,就提交form,具体逻辑请自定义
      setVisible(false)
    }
    const handleCancel = () => {
      setVisible(false)
    }
    return (
      <Modal
        title={title}
        visible={visible}
        onOk={handleOk}
        onCancel={handleCancel}
      >
        {children}
      </Modal>
    )
  }

  return { CustomModal, toggle }
}

export {
  useModal
}
  • 定义:
使用

import { usePrevious } from '@/utils/hooks/use-previous'
import { useModal } from '@/utils/hooks/use-modal' // --------------------------- 引入
import { Button, Form, Input } from 'antd'
import React, { useState } from 'react'


const CustomHooks = () => {
  const { CustomModal, toggle } = useModal('USEMODAL')
  const swtichModal = () => {
    toggle()
  }

  // --------------------------------------------------------------------------- 调用
  return (
        <CustomModal formInstance={form}> 
          <Form
            name="basic"
            initialValues={{ remember: true }}
            form={form} 
          >
            <Form.Item
              label="Username"
              name="username"
            >
              <Input />
            </Form.Item>
          </Form>
          <div>其他内容</div>
        </CustomModal>
  )
}

export default CustomHooks

(3) useFetch

  • 参数: fetch fetchParmas
  • 返回值: data doFetch loading params
useFetch
----


import { useState, useEffect } from 'react';

type Tfetch = (...rest: any[]) => any; // 当函数参数接收任意数量,任意类型的参数时,可以用rest转成any[]类型
interface IfnParams {
  current?: number;
  pageSize?: number;
  total?: number;
  [propNmae: string]: any
}
type TuseFetch = (fetch: Tfetch, fetchParams: IfnParams) => ({
  data: any,
  doFetch: Tfetch,
  loading: boolean;
  params: IfnParams
});


const useFetch: TuseFetch = (
  fetch,
  fetchParams = {
    current: 1,
    pageSize: 8,
    total: 10,
  },
) => {
  const [params, setParams] = useState(fetchParams)
  const [data, setData] = useState<any>(null)
  const [loading, setLoading] = useState(false) // loading有两个作用,一个是防止重复点击,一个是loading动画

  // const memoryFetch = useCallback(fetch, [])
  // const memoryParams = useMemo(() => params, [params])
  // 这里注意:
  // 1. fetch和params是引用类型,不需要在 useEffect中缓存的,因为state本身就做了缓存
  // 2. 但是:如果是常量 aaaaa 是引用类型,在useEffect中就必须用useMemo做缓存,Object.is()永远是false,死循环
  // const aaaaa = {a: 1111}
  // useEffect(() => console.log(aaaaa), [aaaaa]) 死循环

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true)
      try {
        const res = await fetch(params)
        setLoading(false)
        if (res.data) {
          setData(() => res.data)
        }
      } catch (err) {
        setLoading(false)
        console.error(err)
      }
    }

    fetchData()
  }, [fetch, params])


  // doFetch() 用于按钮等重新请求数据
  const doFetch = (fetchParams: IfnParams): void => {
    setParams(fetchParams)
  }


  return { data, doFetch, loading, params }
  // 返回
  // data: 数据
  // doFetch:请求函数
  // loading: 比如用于table的loading
  // params: 比如用于table的分页参数
}

export { useFetch }
项目源码 资料
原文链接:juejin.im

上一篇:[译]分享十五条 JavaScript 编程技巧
下一篇:矩阵链相乘

相关推荐

  • 🍊仿Element自定义Vue组件库

    前言 🍊 市面上目前已有各种各样的UI组件库,他们的强大毋庸置疑。但是有时候我们有必要开发一套属于自己团队的定制化组件库。还有时候原有的组件不能满足我们的各种需求,就需要在原有的组件上进行改造...

    4 个月前
  • 面板拖拽之vue自定义指令

    前言 在指令里获取的this并不是vue对象,vnode.context才是vue对象,一般来说,指令最好不要访问vue上的data,以追求解耦,但是可以通过指令传进来的值去访问method或ref之...

    2 年前
  • 静态网站自定义与构建(二)——VuePress搭建个人博客

    很早之前使用hexo搭建的个人博客由于年久失修逐渐被遗忘,为了激励自己多积累多动笔多思考,所以使用vuepress重新搭建个人博客。相比于hexo,vuepress定制起来也更加灵活,对于熟悉vue的...

    1 个月前
  • 静态网站自定义与构建(一)——静态网站生成器对比

    前言 静态网站和动态网站相比,比较明显的特征就是其不需要后台服务端支持,仅包含html、js、css前端三剑客。常见的静态网站用途有博客系统,文档管理系统,不过我还看到过有老的新闻网站也用静态系统.....

    2 个月前
  • 钉钉小程序 之 自定义组件的使用,以及父组件与子组件(自定义组件)传值

    目录: 本机环境 自定义组件(子组件) 页面中引入(父组件引入子组件) 父组件(使用自定义组件的页面)传值给子组件 子组件(自定义组件)传值给父组件 备注 一、本机环境 本机系统: Mac 小...

    7 个月前
  • 采用shell自定义脚本,控制集成部署环境更加方便灵活

    Jenkins 是一个开源软件项目,旨在提供一个开放易用的软件平台,使软件的持续集成变得可能。现在软件开发追求的是效率以及质量,Jenkins使得自动化成为可能! 亮点 采用shell自定义脚本,控制...

    2 年前
  • 逻辑强化系列(一):彻底搞懂自定义组件使用 v-model

    前言 阅读本文前,希望你已经彻底理解了语法糖 v-model 以及父子组件之间的通讯方法 v-model 在组件上使用 v-model 之前首先要知道,v-model 的用处以及实际操作流程,以方便理...

    6 个月前
  • 通过字符串var [复制]获得自定义对象属性

    CommunityJay-Nicolas Hackleman提出了一个问题:Getting a Custom Objects properties by string var [duplicate],...

    3 年前
  • 通过nodejs实现自定义命令

    我们开发用到的一些库都有自己特有的命令,如webpack,babel和jest等。通过给这些命令输入不同的参数,可以得到相应的功能。通过篇文章,你将学会如何一步步地编写运行在弄的环境的自定义命令。

    3 年前
  • 通过JavaScript的自定义富文本实现短信模板功能

    前言 公司最近有一个发送短信模板的功能实现需求。在网上找到一篇范文,发现并不适合我们的项目,引用了40%的内容,20%的代码,改成了自己的一个封装函数。 此demo在于实现自定义的短信模板,比如有需求...

    1 年前

官方社区

扫码加入 JavaScript 社区