跳到主要内容

79 篇博文 含有标签「前端」

前端开发技术文章

查看所有标签

ESLint 配置

· 阅读需 3 分钟
素明诚
Full stack development

初始化配置

npx eslint --init

a6c2bc6e3a0faa3b8357eac93ff8a713

eslint/create-config 是一个用于帮助创建和管理 ESLint 配置文件的工具包

  • "To check syntax only":只进行语法检查。
  • "To check syntax and find problems":进行语法检查,并找出可能的问题。
  • "To check syntax, find problems, and enforce code style":进行语法检查,找出可能的问题,并强制执行代码风格。

一般选择最后一个,毕竟安装就是为了找出代码问题

继续下一项

76ff7895e0cbc0c5a500b8e3cd6f0513

  • "JavaScript modules (import/export)":这意味着你的项目使用 ES6 模块化规范,也就是使用 importexport 关键字来导入和导出模块。
  • "CommonJS (require/exports)":这意味着你的项目使用 CommonJS 模块化规范,也就是使用 require()module.exports 或者 exports 来导入和导出模块。
  • "None of these":如果你的项目不使用上述任何一种模块化规范,可以选择这个选项。

如果你是 vite 项目,建议选择第一项

继续下一项

4b1b81c538352002d9b22375c9245ec7

选择框架

继续下一项

050647b5e910c246c409281cbaf8896f

是否使用了 TS

选择代码的运行环境,是 node 还是浏览器

继续下一项

5fe4c7a2f7698cdc27fd0bff76fbf027

  • "Use a popular style guide":使用一个流行的代码风格指南。如果你选择这个选项,ESLint 将会让你选择一个流行的代码风格指南(如 Airbnb,Standard,Google 等)作为你的代码风格规则。
  • "Answer questions about your style":回答关于你的代码风格的问题。如果你选择这个选项,ESLint 将会询问你一系列关于代码风格的问题(如你是否使用分号,你的缩进是两个空格还是四个空格等),并根据你的回答生成相应的代码风格规则。

一般选择一个流行的风格指南即可

继续下一项

7c5711029bdb8e04708f2fb48f4fcd8a

  • "JavaScript":你的 ESLint 配置文件将是一个 JavaScript 文件,通常命名为 .eslintrc.js。JavaScript 格式的配置文件允许你使用 JavaScript 代码,例如你可以使用变量和函数,也可以导入其他模块。
  • "YAML":你的 ESLint 配置文件将是一个 YAML 文件,通常命名为 .eslintrc.yaml。YAML 格式的配置文件易于阅读和编写,但不能使用 JavaScript 代码。
  • "JSON":你的 ESLint 配置文件将是一个 JSON 文件,通常命名为 .eslintrc.json。JSON 格式的配置文件也易于阅读和编写,但同样不能使用 JavaScript 代码。

我一般选择第一个

继续下一项

fcffad6c8041d597cda5dd60818838ce

  • eslint-plugin-react:这个包提供了一些特定于 React 的 linting 规则。
  • eslint-config-standard-with-typescript:这个包提供了一个集成了 Standard style 和 TypeScript 的 ESLint 配置。
  • @typescript-eslint/eslint-plugin:这个包包含了一些针对 TypeScript 的 linting 规则。
  • eslint:这是 ESLint 本身。
  • eslint-plugin-import:这个包提供了一些关于 ES6 import/export 语法的 linting 规则。
  • eslint-plugin-node:这个包提供了一些针对 Node.js 的 linting 规则。
  • eslint-plugin-promise:这个包提供了一些关于 Promise 的 linting 规则。
  • typescript:这是 TypeScript 本身。

yes

继续下一项

fe4a5f22862a60d841830522fba4ddf6

喜欢哪个安装哪个

继续下一项

446ed240d15f20d92c7f98555bcf3404

开始安装了

65cef73411dbdcb5302bff3da865c254

结束

React Vue3 API 速查

· 阅读需 3 分钟
素明诚
Full stack development

React 组件 API:

API描述
render()返回需要渲染的元素,或者返回 null
constructor()组件的构造函数,在创建组件的时候调用
componentDidMount()在组件挂载到 DOM 后立即调用
componentDidUpdate()在组件进行更新后立即调用
componentWillUnmount()在组件卸载及销毁之前立即调用
shouldComponentUpdate()通过比较新旧 props 和 state 来决定组件是否应该更新,返回一个布尔值
static getDerivedStateFromProps()当 state 需要从 props 初始化时使用
getSnapshotBeforeUpdate()在更新后,但在 DOM 变更之前调用

React 基础 Hooks:

Hook描述
useState()让函数组件也能有 state(状态)
useEffect()用来替代生命周期函数,如 componentDidMount, componentDidUpdate, componentWillUnmount
useContext()接受一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值

额外的 Hooks:

Hook描述
useReducer()替代 useState,接受一个形如 (state, action) => newState 的 reducer,并返回当前的 state,以及与其配套的 dispatch 方法
useCallback()返回一个记忆的版本,该版本仅在其中的依赖项更改时才会更改
useMemo()返回一个记忆的值,只有当其中的依赖项更改时才会重新计算
useRef()返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)
useImperativeHandle()用于自定义父组件通过 ref 获取和使用子组件的实例属性和方法
useLayoutEffect()其函数签名与 useEffect 相同,但它在所有的 DOM 变更之后同步调用效果
useDebugValue()可以用于在 React DevTools 中显示自定义 hook 的标签

Vue.js 3 常用 API:

API描述
createApp()创建一个新的 Vue 应用实例
defineComponent()定义一个新的组件
watch()响应式地追踪依赖变动
watchEffect()立即执行一个函数并响应式地追踪其依赖,并在其依赖变更时重新运行该函数
computed()创建一个响应式的计算属性

Vue.js 3 组合 API:

API描述
ref()创建一个响应式引用
reactive()接收一个普通对象并返回其代理,等同于 Vue 2.x 的 Vue.observable()
toRefs()将 reactive 创建的响应式对象转化为普通对象
toRef()为 reactive 对象上的属性创建一个 ref
readonly()创建一个只读的响应式对象
isRef()检查一个值是否为一个 ref 对象
isReactive()检查一个对象是否是由 reactive 创建的响应式代理
isReadonly()检查一个对象是否是由 readonly 创建的只读代理
isProxy()检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
unref()如果参数是一个 ref,则返回它的 value,否则返回参数本身
proxyRefs()在 reactive/reactive 上使用 ref
customRef()创建一个自定义的 ref
shallowRef()和 ref 类似,但是只保持浅层响应式
triggerRef()手动触发 shallowRef 的更新
shallowReactive()和 reactive 类似,但只保持浅层响应式
markRaw()使一个对象永远不会被转化为 Proxy

Reactmemo 和 useMemo 应该什么时候使用

· 阅读需 4 分钟
素明诚
Full stack development
  • useMemo 是一个 React Hook,用于在函数组件内部记忆化一个值。当其依赖项改变时,它会重新计算这个值;否则,它会返回上一次计算的值。这可以帮助我们避免在每次渲染时进行昂贵的计算。
  • React.memo 是一个高阶组件,它可以让你控制一个组件何时重新渲染。当一个组件被 React.memo 包装后,只有当它的 props 发生改变时,它才会重新渲染。否则,React 将复用上一次渲染的结果,这可以避免不必要的渲染,提高应用的性能。

这两个功能都是 React 的优化工具,可以帮助我们在保持应用响应性的同时提高性能。但是要注意,他们并不能解决所有的性能问题,且在某些情况下可能会引入额外的性能开销。因此,只有在确定需要进行优化,并且这些工具可以有效地提高性能时,才应该使用他们。

举个例子

当然可以。让我们看一个具体的例子,比如一个简单的列表渲染:

function List({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}

在这个例子中,List 组件接收一个 items prop,并为每个 item 渲染一个列表项。这个组件非常简单,且性能通常是足够的。

然而,如果我们过度优化,可能会这样做:

const ListItem = React.memo(function ListItem({ item }) {
return <li>{item.name}</li>;
});

function List({ items }) {
return (
<ul>
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
}

在这个优化的版本中,我们创建了一个新的 ListItem 组件,并用 React.memo 对它进行了包装,以避免不必要的重新渲染。然而,这种优化实际上可能没有带来实质性的性能提升,因为原始版本的性能已经足够好。更糟糕的是,这种优化增加了代码的复杂性和难以理解性,让代码的维护变得更困难。

此外,React.memo 的浅比较可能会导致问题。在这个例子中,如果 item 对象的属性发生了改变,但 item 对象本身的引用没有改变,那么 React.memo 可能会错过这个变化,导致 UI 不正确。

总的来说,在大多数情况下,我们应该遵循 "先让它工作,然后让它更快" 的原则,也就是说,应该首先关注应用的功能和正确性,然后再关注性能。只有当性能真的成为问题时,我们才需要考虑使用 useMemoReact.memo 进行优化。

我个人觉得这是不是也和 Java 的若无必要,勿增实体的理念很相似,不仅仅适用于 React 或者 JavaScript,也适用于 Java 和其他许多编程语言。这个原则通常被称为 "YAGNI",即 "You Aren't Gonna Need It"(你不会需要它)。这个原则告诫我们避免过度设计和过度优化,除非真的有必要。

所以在使用 memo 的时候还是要多考虑是不是符合以下三点

  1. 不必要的复杂性:如果你的应用或组件并不遇到性能问题,那么引入 useMemoReact.memo 可能会增加不必要的复杂性,让代码更难理解和维护。
  2. 过度优化useMemoReact.memo 通过记忆值和避免不必要的渲染来提高性能,但这种记忆化和避免渲染本身也需要一些计算和内存。如果你过度使用这两个工具,可能会引入额外的性能开销。
  3. 浅比较React.memo 通过对 props 进行浅比较来判断是否需要重新渲染。这意味着如果 props 是复杂的对象,并且只是内部的某个属性发生了变化,React.memo 可能无法正确地判断是否需要重新渲染。

watch 和 watchEffect 区别

· 阅读需 1 分钟
素明诚
Full stack development
  • watchEffect:你提供一个函数,Vue.js 将立即运行该函数,并响应式地追踪其依赖,并在其依赖变更时重新运行该函数。这非常适合侧效应的操作,例如获取数据、订阅或手动改变 DOM 等。你不需要特别指出要观察的对象,Vue.js 会自动收集依赖。

例如:

import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => console.log(count.value))

在上述代码中,每当 count 改变,提供给 watchEffect 的函数就会重新运行。

  • watch:需要明确指出要观察的响应式对象,当被观察的响应式对象发生改变时,watch 将运行一个回调函数。这非常适合在数据变化时执行异步或者较复杂的操作。

例如:

import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newValue, oldValue) => {

console.log('The new count is: ' + newValue)
})

在上述代码中,只有 count 改变时,提供给 watch 的回调函数才会运行。

watchEffect 更为简单和直观,尤其在处理副作用时,而 watch 则提供了更细粒度的控制,包括获取旧值和新值,以及 lazy 选项等

JS中this有什么意义

· 阅读需 2 分钟
素明诚
Full stack development

方法借用:一个对象可以借用另一个对象的方法。例如,我们有一个 person 对象,有一个 introduce 方法可以引用 this.name

let person = {
name: "Alice",
introduce: function() {
console.log(`Hello, my name is ${this.name}`);
}
};

// 这个对象有一个 name 属性,但是没有 introduce 方法
let anotherPerson = {
name: "Bob"
};

// 我们可以借用 person 的 introduce 方法
// 实际意思就是说,我通过call方法,把person上的方法拿来了,在你的身上执行

person.introduce.call(anotherPerson); // 输出 "Hello, my name is Bob"

使用 apply() 以数组形式传递函数参数:如果一个函数接受一组值,而你的值是存储在数组中,apply() 可以帮助你以数组形式传递参数。

function sum(a, b, c) {
return a + b + c;
}

let numbers = [1, 2, 3];

console.log(sum.apply(null, numbers)); // 输出 6

使用 bind() 为事件处理程序创建持久性上下文:在处理事件或回调函数时,你可能想要函数在特定上下文中执行,bind() 可以帮助你实现这一点。

let button = {
content: 'Show message',
clickHandler: function() {
console.log(this.content);
}
};

// 这样做会导致 this 丢失,因为 addEventListener 会调用 clickHandler,将 this 设置为调用它的元素(这里是 button 元素)
// document.querySelector('button').addEventListener('click', button.clickHandler);

// 使用 bind,我们可以确保 clickHandler 内的 this 始终指向 button 对象
document.querySelector('button').addEventListener('click', button.clickHandler.bind(button));

Vue3 中的 provide 和 inject

· 阅读需 2 分钟
素明诚
Full stack development

在 Vue 3 中,provideinject 的值默认是不响应式的。这意味着如果你提供一个值,然后在提供该值之后改变它,那么使用 inject 的组件将不会看到这个变化。

然而,你可以通过 Vue 3 的 reactive 或者 ref 方法来创建响应式的值,然后提供这个响应式的值。这样,当这个值改变时,所有注入该值的组件都会重新渲染,以反映这个变化。

以下是一个例子:

// 在 Vue 3 中的 main.js 或者 main.ts 文件中
import { createApp, reactive } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 创建一个响应式的值
const globalState = reactive({ user: 'John Doe' })

// 提供这个响应式的值
app.provide('globalState', globalState)

app.mount('#app')

然后在任何子组件中,你可以使用 inject API 来接收这个响应式的值,并在模板或者计算属性中使用它:

// 在任何子组件中
import { inject } from 'vue'

export default {
setup() {
const globalState = inject('globalState')

// 在模板或者计算属性中使用 globalState.user
return { globalState }
}
}

在 Vue 3 中,你需要使用 globalState.user 来访问 user 属性,而不能直接使用 globalState,因为 globalState 是一个响应式的对象,而不是一个值。如果你需要提供一个响应式的值(例如一个字符串或数字),你可以使用 ref 方法。

微前端和PNPM工作空间两种方案对比

· 阅读需 3 分钟
素明诚
Full stack development

微前端(Micro Frontends):

微前端是一种设计理念,将前端应用分解成一些较小,更易于管理和交互的部分。每个部分都可以由不同的团队独立开发和部署。

优点:

  1. 每个微应用可以使用最适合它的技术栈。这可以帮助团队利用新技术,也使得代码更加清晰和可维护。
  2. 由于每个微应用都可以独立开发和部署,团队可以更快速地交付新的功能和改进。
  3. 每个微应用可以独立扩展,以满足特定业务需求。

缺点:

  1. 微前端架构可能会导致技术栈的混乱和维护成本的增加。
  2. 有可能遇到跨微应用的数据共享和状态管理问题。
  3. 如果微应用之间需要频繁交互,可能会导致性能问题。

PNPM 工作空间(PNPM Workspaces):

PNPM 工作空间允许您在单一的仓库中管理多个包。它能帮助您在仓库中共享和重用代码,以及更容易地管理多个项目的依赖。

优点:

  1. 更好的代码重用和更高效的依赖管理。
  2. 更容易协调和管理多个项目。
  3. 更容易一次性构建和测试所有的项目。

缺点:

  1. 工作空间可能会导致代码的混乱和复杂性的增加。
  2. 如果没有良好的依赖和版本管理,可能会导致问题。
  3. 与微前端相比,PNPM 工作空间可能不如微前端适应大型项目。

总结起来,这两种方案都有各自的优点和缺点,选择哪一种取决于项目的特性和团队的能力。考虑到这是一个大型的前端项目,由多个小项目组成,我会建议选择微前端。微前端可以让每个小项目独立开发和部署,这对于大型的、由多个小项目组成的项目来说,可以提高开发和部署的效率,同时也使得每个小项目更易于管理和扩展。当然,微前端也有它的挑战,如技术栈的管理和跨应用的数据共享,需要团队有一定的经验和技术能力来解决这些问题。

个人认为选微服务可能后期问题会更少些

对等依赖peer dependency

· 阅读需 2 分钟
素明诚
Full stack development

对等依赖(peer dependency)指的是一个库需要在其工作环境中预先存在一个特定的库或者工具,但并不直接包含或导入这个特定库。这种特定库被称为这个库的对等依赖。

假设你正在开发一个名为 my-plugin 的 Vue.js 插件。这个插件需要在一个已经安装了 Vue.js 的环境中运行,因为它需要使用 Vue.js 的一些特性或 API。然而,my-plugin 并不会直接包含 Vue.js 的代码,它只是假设 Vue.js 已经存在于其运行环境中。在这种情况下,我们说 Vue.js 是 my-plugin 的对等依赖。

当你在 my-pluginpackage.json 文件中声明 Vue.js 为对等依赖时,你在告诉使用 my-plugin 的开发者,他们需要自己手动安装 Vue.js。

对等依赖的一个主要应用场景是插件系统,如上面的例子。插件需要使用主程序(如 Vue.js)的 API,但并不直接包含主程序的代码。通过将主程序声明为对等依赖,插件可以确保自己总是使用与主程序相同的版本,避免版本冲突或者不兼容的问题。

另外,对等依赖也可以用于强制执行某些版本的兼容性。例如,如果 my-plugin 只能和 Vue.js 3.x 兼容,那么你可以在 my-pluginpackage.json 文件中声明 Vue.js 的版本为 "^3.0.0"。这样,如果用户尝试在 Vue.js 2.x 的环境中使用 my-plugin,他们会收到一个警告。

例如,你可以这样声明

5000b9e047739b1338ba0661f58072e3

为什么清除浮动要使用display table

· 阅读需 2 分钟
素明诚
Full stack development

display: table

display: table;:这行代码会将伪元素变成一个块级元素。这样,我们就可以在它上面应用 clear:both。为什么选择 display: table; 而不是 display: block; 呢?

实际上,两者都可以。但是,使用 display: table; 还可以避免某些其他的布局问题,例如外边距(margin)塌缩。

因此,.clearfix::after { content: ""; display: table; clear: both; } 这样的代码可以有效地清除浮动,并避免布局问题。

那边距塌缩又是什么呢?

在 CSS 中,“边距塌缩”是一个常见的现象,即当两个垂直方向的块级元素相邻,并且没有任何内容、padding 或 border 将它们分隔开时,它们之间的间距不会是两个 margin 的总和,而是两个 margin 中的最大值。这就是所谓的“边距塌缩”。

例如,假设我们有两个元素,第一个元素的下边距(margin-bottom)是 20px,第二个元素的上边距(margin-top)是 30px。在边距塌缩的现象下,两个元素之间的距离将是 30px(最大的那个),而不是 20px + 30px = 50px。

那么,为什么display: table;能够防止边距塌缩呢?

这是因为在 CSS 规范中,display: table;会创建一个新的块格式化上下文(Block Formatting Context, BFC)。在新的 BFC 中,内部的元素会在垂直方向上一个接一个地放置,并且可以管理浮动元素、防止外部元素与内部元素重叠,并防止边距塌缩。

这就是为什么要用display: table的好处

npm 安装包时常见参数

· 阅读需 1 分钟
素明诚
Full stack development

-g:全局安装,将包安装到全局环境中,可以在命令行中直接使用

-D(--save-dev):该模块只需要用于开发环境,不用于生产环境,例如测试工具、构建工具等。安装后会被添加到 package.json 文件的 devDependencies 中。

-Y(--yes):在安装模块时,自动选择 yes ,不需要用户确认。

-S(--save):将模块添加到 package.json 文件中的 dependencies 中,该模块在生产环境和开发环境都需要使用。

-E--save-exact:在 package.json 文件中添加精确的版本号,而不是一个范围。

-O--save-optional:将包安装到项目的可选依赖中,同时在 package.json 文件中添加可选依赖项

举例来说,假设你需要安装 Lodash 这个库,如果你只在开发环境使用,可以使用 -D 参数:

npm install lodash -D

如果你需要在生产环境和开发环境都使用,可以使用 -S 参数:

npm install lodash -S

如果你需要在安装模块时自动确认,可以使用 -Y 参数:

npm install lodash -D -Y