hero image

别犹豫 去行动

Just Do It

Tailwind 快速入门

前言:在接触 Tailwind 的刚开始,并没有感受到它的好处,反而觉得这是一种非常繁琐的事情,非常不适应。为了更好的使用 Tailwindcss,便有了该系列:梳理总结使用规律和用法。

章节系列共分为 7 个小节,每小节开头介绍使用规律,再介绍具体使用方法,各自小节独立可依照需求进行查阅。

提示

Unocss 兼容 Tailwind,因此仅需学习 Tailwind 的用法即可。


Huy大约 2 分钟CSSCSS
封装一个简易 fetch 请求库
/**
 * 封装一个 fetch 请求
 * url 请求地址
 * method 请求方式 * GET POST PUT DELETE OPTIONS
 * credentials 携带资源凭证 * include same-origin * omit * credentials
 * headers: null 自定义的请求头信息 「格式必须是纯粹对象」
 * body:null 请求主体信息「只针对 POST 系列请求, 根据当前服务器要求,如果用户传递的是一个纯粹对象, 则需要把其变为 urlencoded 格式字符串(设定请求头中的 Content-Type) 」
 * params: null 设定问号传参信息「格式必须是纯粹对象, 在内部把其拼接到 url 的末尾」
 * responseType: 'json' 请求响应的数据类型 * json text blob arrayBuffer
 * timeout: 5000 请求超时时间
 * signal: 中断请求的信号
 */
const http = function (config) {
  if (typeof config !== 'object') config = {}

  config = Object.assign(
    {
      url: '',
      method: 'GET',
      credentials: 'include',
      headers: null,
      body: null,
      params: null,
      responseType: 'json',
      timeout: 5000,
      signal: null,
    },
    config
  )

  // 必要参数判断
  if (!config.url) {
    throw new Error('url is required')
  }
  if (typeof config.headers !== 'object') config.headers = {}
  if (config.params !== null && typeof config.params !== 'object')
    config.params = null

  // 处理细节
  let {
    url,
    method,
    credentials,
    headers,
    body,
    params,
    responseType,
    timeout,
  } = config

  // 处理问号传参
  if (params) {
    let paramsStr = Object.keys(params)
      .map((key) => `${key}=${params[key]}`)
      .join('&')
    url += `${url.includes('?') ? '&' : '?'}${paramsStr}` // 拼接时是否带问号
  }

  // 处理请求主体信息
  if (typeof body === 'object') {
    body = JSON.stringify(body)
    headers['Content-Type'] = 'application/json'
  }

  const token = localStorage.getItem('token')
  if (token) {
    headers['Authorization'] = `Bearer ${token}`
  }

  // 超时处理
  if (signal === null) {
    signal = new AbortController().signal
  }
  const timeoutId = setTimeout(() => {
    signal.abort()
  }, timeout)

  // 发起请求
  method = method.toUpperCase()
  config = {
    method,
    credentials,
    headers,
    cache: 'no-cache',
    signal,
  }

  if (['POST', 'PUT', 'PATCH'].includes(method)) {
    config.body = body
  }

  return fetch(url, config)
    .then((response) => {
      const { status, statusText } = response
      if (status >= 200 && status < 300) {
        // 请求成功
        let result

        switch (responseType.toLowerCase()) {
          case 'json':
            result = response.json()
            break
          case 'text':
            result = response.text()
            break
          case 'blob':
            result = response.blob()
            break
          case 'arraybuffer':
            result = response.arrayBuffer()
            break
          default:
            result = response.json()
        }

        return response.json()
      }

      // 请求失败: HTTP 状态码失败
      return Promise.reject({
        code: -100,
        status,
        statusText,
      })
    })
    .catch((reason) => {
      const { code, status } = reason
      if (code === -100) {
        switch (+status) {
          case 401:
            console.error('未授权,请重新登录!')
            break
          case 403:
            console.error('禁止访问!')
            break
          case 404:
            console.error('请求的资源不存在!')
            break
          case 500:
            console.error('服务器内部错误,请稍后再试!')
            break
          default:
            console.error('当前网络繁忙,请稍后再试!')
        }
      }
      return Promise.reject(reason)
    })
}

// 快捷方法
// ["GET", "HEAD", "DELETE", "OPTIONS"].forEach(method => {
//   http[method.toLowerCase()] = function(url, config) {
//     if (typeof config !== 'object') {
//       config['url'] = url
//       config['method'] = method
//       return http(config)
//     }
//   }
// })

// ["POST", "PUT", "PATCH"].forEach(method => {
//   http[method.toLowerCase()] = function(url, body, config) {
//     if (typeof config !== 'object') {
//       config['url'] = url
//       config['method'] = method
//       config["body"] = body;
//       return http(config)
//     }
//   }
// })

export default http

Huy大约 2 分钟javascriptjavascript
React之MobX

简单入门一下 Mobx~

MobX 是一个用于 JavaScript 应用程序的状态管理库,它通过响应式编程原则简化和扩展了状态管理。它在 React 应用程序中特别受欢迎,但也可以与任何 JavaScript 框架或库一起使用。

Mobx
Mobx

Huy大约 6 分钟框架React
Koa 异步调用中间件的本质

Koa 是通过将中间件组织成一个“洋葱模型”(Onion Model),并使用 async/awaitPromise 链式执行机制实现异步中间件的。

核心机制

  1. 中间件存储:

    • 中间件被存储为一个数组(middlewares)。
    • 每个中间件是一个函数,接受 ctx(上下文)和 next(下一个中间件的执行函数)作为参数。
  2. 组合中间件(compose):

    • Koa 使用一个函数(类似于 compose)将多个中间件组合成一个函数,并按顺序执行。
    • 每个中间件调用 await next() 来手动控制下一个中间件的执行时机。
  3. 递归调用:

    • 当一个中间件调用 await next() 时,它会等待下一个中间件执行完成后再继续执行当前中间件后面的逻辑。

Huy大约 2 分钟javascriptnode
React之技术细节

这里用于梳理 React 的一些技术实现细节,以作技术回顾。

setState 的更新逻辑

这里主要讨论的是 React18 以前的策略。React18 之后,全部采用异步调用。可见setstate-的更新逻辑

React18 以前,setState 的更新逻辑有时是同步的有时是异步的,这取决于调用 setState 的环境。实际上,在 React 控制之内的事件处理过程中,setState 是异步的,而在 React 控制之外的事件处理过程中,setState 是同步的。


Huy大约 2 分钟框架React
Cesium Property 属性机制

在此前系列中,笔者梳理了 Cesium 的基本使用方法,但是为了更进阶了解 Cesium,我们需要了解 Cesium 的核心机制,其中之一就是 Property 属性机制。

先总览一遍 Property 类型分类。

Property 类型
Property 类型

Huy大约 12 分钟框架gis
批量修复 git 分支问题

最新需要批量修复 git 分支问题,记录一下。

需求是有很多项目分支存在一些共性 bug,需要批量修复这些分支。每个分支都 cherry-pick 或者 merge 的话又太费时间。因此有了这个脚本实现,顺便也学习一下 bash 的一些语法。

最终效果如下,有合并进度条、状态表格等提示输出:

=========================================
        🌿 Starting the Merge Process
=========================================
[#############                           ]  33% ➜ Switching to feature/branch1...
[#############                           ]  33% ➜ Merging fix/common-issue into feature/branch1...
 ✔ Successfully merged into feature/branch1.
 ➜ Attempting to push feature/branch1 to remote...
 ✘ Failed to push feature/branch1 to remote.
[##########################              ]  66% ➜ Switching to feature/branch2...
[##########################              ]  66% ➜ Merging fix/common-issue into feature/branch2...
 ✘ Merge conflict detected in feature/branch2!
 ➜ Aborting merge and restoring clean working directory...
[########################################] 100% ➜ Switching to feature/branch3...
[########################################] 100% ➜ Merging fix/common-issue into feature/branch3...
 ✔ Successfully merged into feature/branch3.
 ➜ Attempting to push feature/branch3 to remote...
 ✘ Failed to push feature/branch3 to remote.
=========================================
Merge Summary:
=========================================
Branch                    | Status
=========================================
feature/branch1           | ✘ PushFailed
feature/branch2           | ✘ Conflict
feature/branch3           | ✘ PushFailed
=========================================
 ➜ Returning to the fix branch...
=========================================
Merge process completed.

Huy大约 9 分钟javascriptjavascript
检查多语言重复

最近需要检查多语言文件中是否有重复的 key,于是写了一个 Node.js 脚本来实现这个功能。

这个脚本会在 git 提交代码时,对提交的多语言文件进行校验。通过遍历指定目录下的所有 JSON 文件,检查每个文件中的 key 是否有重复,并将结果输出到控制台。同时,脚本还会将所有 key 存入一个缓存文件中,以便下次运行时可以快速查询。

实现逻辑

理论依据:由于 lint-staged 会将暂存区中被修改的文件路径作为参数传入定义的脚本。这意味着可以在 lint-staged 中配置的命令中直接访问提交的文件列表,而不需要手动指定文件路径。


Huy大约 5 分钟javascriptnode
虚拟dom转真实dom

将下面的虚拟 dom 转换为真实 dom:

const vnode = {
  tag: 'div',
  attrs: {
    id: 'app',
  },
  children: [
    {
      tag: 'span',
      children: [
        {
          tag: 'a',
          text: 'hello',
          children: [],
        },
      ],
    },
    {
      tag: 'span',
      children: [
        {
          tag: 'a',
          text: 'world',
          children: [],
        },
        {
          tag: 'a',
          text: 'javascript',
          children: [],
        },
      ],
    },
  ],
}
render(vnode, document.querySelector('#root'))

function render(vnode, container) {}

Huy小于 1 分钟javascriptjavascript
手写简易 Commonjs

CommonJS

概念

CommonJS 是一种模块化规范,它允许我们在一个文件中定义模块,然后在另一个文件中引入和使用这些模块。CommonJS 是运行时的这点和 ESModule 不同。

可以简单思考一下,这个文件导出后的结果是什么?

// commonjs 待导出文件
this.a = 1
exports.b = 2
exports = {
  c: 3,
}

module.exports = {
  d: 4,
}

exports.e = 5
this.f = 6

Huy大约 2 分钟javascriptnode
2
3
4
5
...
17

Yesterday is history, tomorrow is a mystery, today is a gift of God, which is why we call it the present.