GithubHelp home page GithubHelp logo

code-more's Introduction

code-more's People

Contributors

heycn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

code-more's Issues

实时对比服务器时间间隔

解决当前系统时间与服务器时间对比不准确问题

背景

有一个需求,需要对服务器某个时间进行对比,并在页面实时展示,但是当用户修改系统时间时,就会导致结果不准确

解决

需要服务端返回 对比时间服务器时间,使用 moment 进行对比,自定义 secondToTimeuseInterval 用于格式化时间和计时

import React, { useEffect, useState, useRef } from 'react'
import moment from 'moment'

const secondToTime = second => {
  if (!second) {
    return 0 + _t('分钟')
  } else if (second < 60) {
    return second + _t('秒')
  } else if (second >= 60 && second < 3600) {
    let sec = second % 60
    let min = (second - sec) / 60
    if (sec == 0) {
      return min + _t('分钟')
    } else {
      return _t('{{min}}分{{sec}}秒', {min, sec})
    }
  } else if (second >= 3600) {
    let min = Math.floor(second / 60)
    let hour = Math.floor(min / 60)
    min = min % 60
    if (min == 0) {
      return hour + _t('小时')
    }
    return hour + _t('小时') + min + _t('分钟')
  }
}

const useInterval = (callback, delay) => {
  const savedCallback = useRef()

  useEffect(() => {
    savedCallback.current = callback
  }, [callback])

  useEffect(() => {
    function tick() {
      savedCallback.current()
    }
    if (delay !== null) {
      let id = setInterval(tick, delay)
      return () => clearInterval(id)
    }
  }, [delay])
}

const App = ({flagTime, serverTime})=> {
  const [flagTime] = React.useState(flagTime)
  const [serverTime, setServerTime] = React.useState(serverTime)

  useInterval(() => {
    let n = 0
    setServerTime(moment(serverTime).add(n+=1, 's'))
  }, 1000)

  const runderTime = () => {
    if (flagTime && serverTime) {
      let diff = moment(serverTime).diff(moment(), 's')
      if (diff > 30 || diff < -30) {
        return secondToTime(moment(serverTime).diff(flagTime, 's'))
      } else {
        return secondToTime(moment().diff(flagTime, 's'))
      }
    } else {
      return ''
    }
  }

  return (
    <div>{runderTime}</div>
  )
}

这样子就可以尽可能地实时对比时间了

React 自定义 hook 实现验证码倒计时

避免陷入闭包陷阱

import React, {useEffect, useState} from 'react'

const useCountDown = second => {
  const [seconds, setSeconds] = useState(second)
  useEffect(() => {
    setTimeout(() => {
      if (seconds > 0) {
        setSeconds(seconds - 1)
      }
    }, 1000)
  }, [seconds])

  return [seconds, setSeconds]
}

const App = () => {
  const [seconds, setSeconds] = useCountDown(0)
  return (
    <button disabled={seconds !== 0} onClick={() => setSeconds(60)}>
      {seconds > 0 ? `${seconds}s后可点击` : '点击开始倒计时'}
    </button>
  )
}

关于递归及优化

递归例子

阶乘

j = n => n === 1 ? 1 : n * j(n-1)

斐波那契数列

fib = n =>
  n === 0 ? 0 :
  n === 1 ? 1 :
  fib(n-1) + fib(n-2)

注意:每次进入函数需要往 调用栈 里「压栈」,调用栈 用来记录「回到哪」,如果需要记录得过多,就会「爆栈」。

所以,需要降低「压栈」或者计算次数,以下是优化方案

优化手段

尾递归优化

使用迭代代替递归

fib = n => fib_inner()

fib_inner = (start, end, prev1, prev2) =>
  start === end ? prev1 + prev2
    : fib_inner(start+1, end, prev1+prev2, prev1)

这就是「尾递归」

为什么尾递归可以优化?因为不用「压栈」了,「压栈」的目的是为了记录回到哪,那如果我不回来,那我就不需要「压栈」了。

注意:但是!JS 有 Bug,就算你不压栈,它还是会把一些不相关的操作压进去(例如环境),所以,JS 不存在「尾递归优化」!
所以,以上代码,依然会「压栈」,虽然会快一些,但是没有什么区别。

改写成循环

所有的递归都可以改写成循环

fib_loop = n => {
  let array = [0, 1]
  for(let i = 0; i <= n-2; i++) {
    array[i+2] = array[i+1] + array[i]
  }
  return array[array.length-1]
}

以上代码把结果都记录在数组里面,那么就不需要重复的计算某些值了。

以下是 ChatGPT 对以上代码的解释:

以上代码是一个计算斐波那契数列第 n 项的函数,采用了循环方式来计算。具体解释如下:

1. `fib_loop = n => {...}`:定义一个名为 `fib_loop` 的函数,它接受一个参数 `n`,使用箭头函数方式定义。
2. `let array = [0, 1]`:初始化一个数组 `array`,它包含斐波那契数列的初始两个元素。
3. `for(let i = 0; i <= n-2; i++) {...}`:循环遍历 `array` 数组,从第3项开始依次计算出斐波那契数列的每一项,直到计算出第n项。
4. `array[i+2] = array[i+1] + array[i]`:计算斐波那契数列的每一项,并存储在 `array` 数组中。
5. `return array[array.length-1]`:返回 `array` 数组的最后一项,也就是斐波那契数列的第 n 项。

递归好像没什么用?

使用循环就能解决,这样看起来递归很无用呀?是的,有时候递归就是这样。

因为 JS 在递归方面就没有提供很好的工具,所以在 JS 中写递归会让你的同事看不懂,并且性能又差,那怎么办呢?

使用「记忆化」 —— 这是一个很好的解决办法。

记忆化

如果我曾经算过了,那就不要算了

可以减少重复计算,大大减低压栈次数

以下是 Lodash 记忆化函数的实现:lodash/memoize.js

封装 LongPressable,监听长按事件

组件封装

LongPressable.tsx

import type { ReactNode, TouchEvent } from 'react'
import { useRef } from 'react'

type Props = {
  children: ReactNode
  onEnd?: () => void
}
export const LongPressable: React.FC<Props> = (props) => {
  const { children, onEnd } = props
  const touchTimer = useRef<number>()
  const touchPosition = useRef<{ x?: number; y?: number }>({ x: undefined, y: undefined })
  const onTouchStart = (e: TouchEvent) => {
    touchTimer.current = window.setTimeout(() => {
      onEnd?.()
    }, 500)
    const { clientX: x, clientY: y } = e.touches[0]
    touchPosition.current = { x, y }
  }
  const onTouchMove = (e: TouchEvent) => {
    const { clientX: newX, clientY: newY } = e.touches[0]
    const { x, y } = touchPosition.current
    if (x === undefined || y === undefined) { return }
    const distance = Math.sqrt((newX - x) ** 2 + (newY - y) ** 2)
    if (distance > 10) {
      window.clearTimeout(touchTimer.current)
      touchTimer.current = undefined
    }
  }
  const onTouchEnd = (e: TouchEvent) => {
    if (touchTimer.current) {
      window.clearTimeout(touchTimer.current)
      touchTimer.current = undefined
    }
  }
  return (
    <div onTouchStart={onTouchStart} onTouchMove={onTouchMove} onTouchEnd={onTouchEnd}>
      {children}
    </div>
  )
}

使用

<LongPressable onEnd={() => console.log(long pressable!)}>
  <h1>long pressable me!</h1>
</LongPressable>

我的自定义校验规则

interface Data {
  [k: string | number]: JSONValue
}
type Rule<T> = {
  key: keyof T
  message: string
} & (
    { type: 'required' } |
    { type: 'chinese' } |
    { type: 'equalField'; field: keyof T } |
    { type: 'pattern'; regex: RegExp } |
    { type: 'notEqual'; value: JSONValue } |
    { type: 'length'; min?: number; max?: number }
  )
type Rules<T> = Rule<T>[]

type FormError<T> = {
  [k in keyof T]?: string[]
}
export type { Rules, Rule, Data, FormError }

export const validate = <T extends Data>(formData: T, rules: Rules<T>): FormError<T> => {
  const error: FormError<T> = {}
  rules.forEach((rule) => {
    const { key, type, message } = rule
    const value = formData[key]
    switch (type) {
      case 'required':
        if (isEmpty(value)) {
          error[key] = error[key] ?? []
          error[key]?.push(message)
        }
        break
      case 'pattern':
        if (!isEmpty(value) && !rule.regex.test(value!.toString())) {
          error[key] = error[key] ?? []
          error[key]?.push(message)
        }
        break
      case 'notEqual':
        if (!isEmpty(value) && value === rule.value) {
          error[key] = error[key] ?? []
          error[key]?.push(message)
        }
        break
      case 'length':
        if (!isEmpty(value)) {
          if (rule.min && value!.toString().length < rule.min) {
            error[key] = error[key] ?? []
            error[key]?.push(message)
          }
          if (rule.max && value!.toString().length > rule.max) {
            error[key] = error[key] ?? []
            error[key]?.push(message)
          }
        }
        break
      case 'chinese':
        if (!isEmpty(value) && !/^[\u4E00-\u9FA5]+$/.test(value!.toString())) {
          error[key] = error[key] ?? []
          error[key]?.push(message)
        }
        break
      case 'equalField':
        if (!isEmpty(value) && value !== formData[rule.field]) {
          error[key] = error[key] ?? []
          error[key]?.push(message)
        }
        break
      default:
        break
    }
  })
  return error
}

function isEmpty(value: undefined | JSONValue | Data) {
  return value === null
    || value === undefined
    || value === ''
    || (Array.isArray(value) && value.length === 0)
}

export function hasError(errors?: Record<string, string[]>) {
  // return Object.values(errors)
  // .reduce((result, value) => result + value.length, 0) > 0
  if (!errors) { return false }
  let result = false
  for (const key in errors) {
    if (errors[key]?.length > 0) {
      result = true
      break
    }
  }
  return result
}

理解「函数柯里化」

如果没有接受函数式的一整套理论,就没有必要用柯里化

柯里化 Currying:让所有函数只接受一个参数

有什么意义?

我们把一个只接受一个参数的函数叫作「单参数函数」,基于这种函数,已经衍生出非常多的理论知识,而且是关于数学方面的,跟编程关系不大

λ演算 —— lambda演算

假设一个函数只支持一个参数

那么支持接受两个参数呢?

  1. 使用对象
  2. 使用闭包

一般来说,在函数式编程中,不使用对象,而是使用闭包,因为 —— 对象是穷人的闭包
如果不理解上面这句话,可以看下我写过的一篇博客对象是穷人的闭包,闭包是穷人的对象

我们有闭包了,就不需要使用对象

使用对象

const add = ({a, b}) => a + b
add({a: 1, b: 2})

上面这种方式在函数式编程中,特别的傻

使用闭包

const add = a => b => a + b
add(1)(2)

如果没见过上面这种写法,是不是看不太习惯?或者会疑问:闭包在哪里呢?
请继续往下看:

// 以上的代码,基本等价与下面的代码
add = function(a) {
  return function(b) {
    return a + b // 这个 a 就是和这个函数形成了闭包
  }
}
add(1)(2)

但是如果只写过 JS 的人,会感觉有点不方便对不对?

没错,如果没有接受过函数式的一整套理论,你会觉得这就是在耍花招,没什么用
但是你如果接受过一系列函数式的东西,你就会觉得柯里化是非常重要的部分,如果没学过,那就没必要用柯里化

柯里化一个函数

初级

请把三参数函数 add(1,2,3) 变成 curriedAdd(1)(2)(3) 形式:

const curriedAdd =
  a =>
    b =>
      c =>
        add(a, b, c)

完毕,非常的简单。

升级

假设:

  • addTwo 接受两个参数
  • addThree 接受三个参数
  • addFour 接受四个参数
  • 如何写出一个 currify 函数,使得他们分别接受 2、3、4 次参数,如:
    • currify(addTow)(1)(2) // 3
    • currify(addThree)(1)(2)(3) // 6
    • currify(addFour)(1)(2)(3)(4) // 10

也就是说,currify 函数能将任意接受固定个参数的函数,变成单一参数的函数

const currify = (fn, params = []) => {
  return (arg) => {
    const newParams = params.concat(arg)
    if(newParams.length === fn.length) {
      return fn(...newParams)
    } else {
      return currify(fn, newParams)
    }
  }
}

const addTwo = (a, b) => a + b

再升级

我想支持 currify(addFour)(1)(2,3)(4) // 10

那么需要这么改写

const currify = (fn, params = []) => {
- return (arg) => {
+ return (...args) => {
-   const newParams = params.concat(arg)
-   if(newParams.length === fn.length) {
+   if(params.length + args.length === fn.length) {
-     return fn(...newParams)
+     return fn(...params, ...args)
    } else {
-     return currify(fn, newParams)
+     return currify(fn, [...params, ...args])
    }
  }
}

最终简化

const currify = (fn, params = []) => 
  (...args) =>
    params.length + args.length === fn.length
      ? fn(...params, ...args)
      : currify(fn, [...params, ...args])

这样,是不是对「柯里化函数」有一点点理解了呢?

封装 useSwipe,支持左右滑动事件

useSwipe.tsx

import type { RefObject } from 'react'
import { useEffect, useRef, useState } from 'react'

interface Config {
  onTouchStart?: (e: TouchEvent) => void
  onTouchMove?: (e: TouchEvent) => void
  onTouchEnd?: (e: TouchEvent) => void
}

export const useSwipe = (elementRef: RefObject<HTMLElement>, config?: Config) => {
  const [direction, setDirection] = useState<'' | 'left' | 'right'>('')
  const startX = useRef(-1)

  const onTouchStart = (e: TouchEvent) => {
    config?.onTouchStart?.(e)
    startX.current = e.touches[0].clientX
  }
  const onTouchMove = (e: TouchEvent) => {
    config?.onTouchMove?.(e)
    const newX = e.touches[0].clientX
    const distance = newX - startX.current
    if (Math.abs(distance) < 3) setDirection('')
    if (distance > 0) setDirection('right')
    if (distance < 0) setDirection('left')
  }
  const onTouchEnd = (e: TouchEvent) => {
    config?.onTouchEnd?.(e)
    setDirection('')
  }

  useEffect(() => {
    if (!elementRef.current) return
    elementRef.current.addEventListener('touchstart', onTouchStart)
    elementRef.current.addEventListener('touchmove', onTouchMove)
    elementRef.current.addEventListener('touchend', onTouchEnd)
    return () => {
      if (!elementRef.current) return
      elementRef.current.removeEventListener('touchstart', onTouchStart)
      elementRef.current.removeEventListener('touchmove', onTouchMove)
      elementRef.current.removeEventListener('touchend', onTouchEnd)
    }
  }, [])
  return direction
}

使用:

  const swipeDiv = useRef<HTMLDivElement>(null)
  const direction = useSwipe(swipeDiv, { onTouchStart: e => e.preventDefault() })

封装原生 popover

封装原生 popover

JavaScript

const createPopover = text => {
  if (!text) return null
  const popoverWrapper = document.createElement('div')
  const popover = document.createElement('div')
  popoverWrapper.classList.add('popoverWrapper')
  popover.innerHTML = text
  popover.classList.add('popover')
  popover.style.maxWidth = '240px'
  popoverWrapper.appendChild(popover)
  return popoverWrapper
}

// 当 DOMContentLoaded 事件触发时执行的回调函数
document.addEventListener('DOMContentLoaded', () => {
  // 获取所有具有 data-popover-text 属性的元素,并遍历每个元素
  const elementsWithPopover = document.querySelectorAll('[data-popover-text]')
  elementsWithPopover.forEach(element => {
    // 获取 element 元素的 data-popover-text 属性值,并赋值给 popoverText 变量
    const popoverText = element.dataset.popoverText
    // 创建一个包含指定文本内容的 popover 元素,并赋值给 popover 变量
    const popover = createPopover(popoverText)
    // 如果 popover 存在,则执行以下代码
    if (popover) {
      // 隐藏 popover
      popover.style.opacity = '0'
      popover.style.pointerEvents = 'none'
      // 将 popover 添加到 element 的父元素中
      const parent = element.parentElement
      parent.classList.add('relative') // 自动添加相对定位
      parent.style.cursor = 'pointer' // 自动添加鼠标指针样式
      parent.appendChild(popover)
      // 当鼠标移入 element 元素时,显示 popover,并计算 popover 的位置
      element.addEventListener('mouseenter', () => {
        popover.style.opacity = '1'
        popover.style.transition = 'opacity 0.35s'
        popover.style.pointerEvents = 'auto'
        const { top, left, height } = element.getBoundingClientRect()
        const popoverHeight = popover.offsetHeight
        const newTop = top + window.pageYOffset - (popoverHeight - height) / 2
        const newLeft = left - popover.offsetWidth
        popover.style.top = `${newTop}px`
        popover.style.left = `${newLeft - 12}px`
      })
      // 当鼠标移出 element 元素时,隐藏 popover
      element.addEventListener('mouseleave', () => {
        popover.style.opacity = '0'
        popover.style.transition = 'opacity 0.35s'
        popover.style.pointerEvents = 'none'
      })
    }
  })
})

如何使用

在 HTML 里使用:

<aside>
  <p data-popover-text="This is popover content...">Hover Me!</p>
</aside>

效果预览

141679892895_ pic

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.