跳到主要内容位置
Hello! 我是张旭乾

致力于帮助你以最直观、最快速的方式学会前端开发,并希望我的个人经历对你有所启发。点击查看最新技术视频教程、实战课程、技术博客、前端资源导航、以及我的想法和生活点滴

“峰华前端工程师”账号创作者/《JavaScript 基础语法详解》作者

最新视频

React 作为一个流行了很久的框架,在使用方式上,仍然需要有很多注意的地方,这个视频整理了 6 个推荐的 React 代码写法,来帮助你编写效率更高,更方便复用的组件。

使用函数式组件

第一个是使用函数式组件。React 在 16.8 版本的时候发布了 Hooks,在同一时间,class 组件就不推荐使用了。因为 class 组件的代码写起来比较繁琐,例如必须使用构造函数来获取 props 和定义 state,使用 render() 方法来返回 JSX。并且 class 组件有多种生命周期,相关的代码会分散在不同的方法中,不方便统一管理:

import React, { Component } from "react";

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      // Initial state
    };
  }

  componentDidMount() {
    // componentDidMount lifecycle method
  }

  componentDidUpdate(prevProps, prevState) {
    // componentDidUpdate lifecycle method
  }

  componentWillUnmount() {
    // componentWillUnmount lifecycle method
  }

  render() {
    return <div>{/* component JSX */}</div>;
  }
}

export default MyComponent;

使用函数式组件后,就和写普通 JavaScript 函数差不多,props 通过函数的参数获取,在返回语句中返回 JSX,其他的逻辑在函数体中直接编写就可以了,并且函数式组件没有生命周期,只需要使用 useEffect() hook 来控制组件的重新渲染就可以了:

import React from "react";

const MyComponent = (props) => {
  // Use hooks here, if needed
  useState();
  useEffect();
  // ...

  return <div>{/* component JSX */}</div>;
};

export default MyComponent;

利用 hooks 复用逻辑

第二个是利用 hooks 复用逻辑。hooks 的出现就是为了复用组件的业务逻辑的,大体上组件函数里所有的代码都可以放到 hooks 中:

import { useState } from "react";

export const useCounter = (initialCount = 0) => {
  const [count, setCount] = useState(initialCount);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(initialCount);

  return { count, increment, decrement, reset };
};

在其他组件可以直接导入这些 hooks 并使用:

import React from "react";
import { useCounter } from "./useCounter";

const MyComponent = () => {
  const { count, increment, decrement, reset } = useCounter();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

export default MyComponent;
import React from "react";
import { useCounter } from "./useCounter";

const MyOtherComponent = () => {
  const { count, increment, decrement, reset } = useCounter();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

export default MyOtherComponent;

hooks 的出现也代替了旧版本中的 higher order components 和 render props 的写法。

少使用 state

第三是尽量少使用 state:

  • state 的变化会引起整个组件树的刷新,会带来一些性能上的损失,虽然 React 18 优化了 state 的批量更新,但同样有性能消耗。
  • state 的变化也比较难追踪到,调试起来比较困难。
  • 另外 state 会分散在不同的组件中,也不利于代码的维护

在编写组件时,只有在必须使用 state 的情况下再定义,我们可以通过 hooks 来减少状态的分散,例如使用 useReducer 结合 context 来集中管理状态,对于能经过其他状态计算而得出的数据,也不必定义为单独的状态。

定义可复用的组件

第四是定义可复用的组件来减少代码量,通过 props 来实现组件的自定义,利用 children 来方便组件之间的组合。组件里边可以根据 props 来展示不同的内容,或者处理不同的事件:

import React from "react";

const Button = ({ onClick, children }) => (
  <button onClick={onClick}>{children}</button>
);

export default Button;
import React from "react";
import Button from "./Button";

const MyComponent = () => (
  <div>
    <Button onClick={() => alert("Button clicked!")}>Click me</Button>
  </div>
);

export default MyComponent;

如果把这些都写在组件内部,那么这个组件就不能被其他组件复用,从而变成了一个只能处理一种情况的特殊组件。

import React from "react";

const Button = () => (
  <button onClick={() => alert("Button clicked")}>Click me</button>
);

export default Button;

利用 children 减少嵌套

第五个是利用 children 来减少组件之间的嵌套。当组件嵌套层次过多的时候,我们需要层层传递 props 到最深处的组件,虽然可以利用全局状态管理或者是 context 来传递,但毕竟需要额外的代码和配置:

import React from "react";

const CardContent = ({ content }) => <p>{content}</p>;

const SimpleCard = ({ content }) => (
  <div>
    <h1>Simple Card</h1>
    <CardContent content={content} />
  </div>
);

const App = () => <SimpleCard content="This is a card." />;

export default App;

更简单的解决方法就是利用 children。这样我们可以直接在最上层的组件来组装它们,之后传递 props 或者是 state 等等之类的就很方便了:

import React from "react";

const CardContent = ({ content }) => <p>{content}</p>;

const SimpleCard = ({ children }) => (
  <div>
    <h1>Simple Card</h1>
    <div>{children}</div>
  </div>
);

const App = () => (
  <SimpleCard>
    <CardContent content="This is a card." />
  </SimpleCard>
);

export default App;

避免不必要的 props

第六个是避免传递不必要的 props。尤其是当传递一个数据量比较大的 prop 的时候,它会占据很多的内存空间,并且影响性能。所以应该只传递子组件能够接收的 props:

const MyComponent = ({ prop1, prop2 }) => {
  return (
    <div>
      {prop1}
      {prop2}
    </div>
  );
};

const App = () => <MyComponent prop1="Hello" prop2="World" />;
// 传递不必要的大数据量:
const App = () => (
  <MyComponent prop1="Hello" prop2="World" prop3={ /* large data */ } />
);

小结

这个就是在编写 react 项目时的 6 个推荐的实践。你学会了吗?如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

React 开发 6 个需要注意的地方

JavaScript Temporal 日期和时间 API 是用来替代 Date 对象的,现在处于 stage 3 阶段,浏览器和 Node.js 正在对它进行实现。 Temporal API 解决了 Date 对象的一些缺点,例如:

  • 不支持时区,需要手动进行转换。
  • 只支持公历,不支持其他日历,例如我国的农历。
  • 不同浏览器对于同一个时间的计算方式不同,尤其是对于有夏令时的地区。
  • 日期的展示方式对于用户来说不够友好。
  • Date 对象是可变的,容易造成 bug。

这些问题在 Temporal API 中都得到了解决,接下来我们看一下 Temporal API 的常见的使用方法。

配置项目安装 polyfill

因为截止到目前 Temporal API 还没正式发布,我们需要个 Polyfill 来使用它。这里创建了一个 Node.js 项目,然后添加了 @js-temporal/polyfill这个 polyfill 依赖。 之后在 index.js 中导入它提供的 Temporal 对象:

const { Temporal } = require("@js-temporal/polyfill");

获取当前日期

获取当前时间可以通过 Temporal.Now.instance() 方法,这里注意这个 polyfill 需要我们手动调用 toString() 来获取日期字符串,并且这个日期是无关时区的:

console.log(
  "Current date and time(without a specific time zone): ",
  currentTime.toString()
);

我们也可以分别通过 epochMilliseconds 和 epochNanoseconds 来分别获取当前时间的毫秒数和纳秒数。Temporal 的时间精度可以精确到纳秒:

console.log("milliseconds: ", currentTime.epochMilliseconds);
console.log("nanoseconds: ", currentTime.epochNanoseconds);

创建自定义日期

Temporal 也支持创建自定义日期,可以调用 Temporal.PlainDateTime.from() 方法,然后传递一个对象参数,里边可以通过 year、 month、day、hour、minute 和 second 属性来分别指定年月日和时分秒:

const dateTime = Temporal.PlainDateTime.from({
  year: 2018,
  month: 2, // no longer need to plus 1
  day: 10,
  hour: 8,
  minute: 12,
  second: 48,
});

console.log(dateTime.toString());

另外也可以通过 Temporal.PlainDate 和 Temporal.PlainTime 来分别创建指定日期或时间。

修改日期

要修改日期的话,Temporal 提供了很方便的方法,可以调用 add() 方法来向后推移日期,也可以调用 substract() 向前推移,这两个方法都接收一个对象,里边的参数和创建日期时的一样,可以对年月日时分秒进行增加或减少,需要注意的是 temporal 对象是不可变的,需要把返回值保存到新的变量里边来获取修改后的日期:

const newDateTime = dateTime.add({ months: 1 });
console.log(newDateTime.toString());

比较日期

Temporal 还提供了 since() 方法来比较两个日期,比较的结果是一个对象,可以通过它可以获取相差的天数、月数、小时数等不同维度的时间:

const dt1 = Temporal.PlainDate.from("2023-02-01");
const dt2 = Temporal.PlainDate.from("2023-02-05");
const diff = dt2.since(dt1);
console.log(diff.days);

格式化日期

Temporal API 可以直接获取指定地区日期的特定格式,如果要完全自定义日期格式,还是需要分别获取年月日时分秒,来自行组装:

console.log(`${dt1.year}/${dt1.month}/${dt1.day}`);

获取指定时区的时间

使用 Temporal 获取的时间是无关时区的,如果要获取对应时区的时间,可以在获取当前时间后,调用 toZonedDateTimeISO() 方法,它接收 Temporal.TimeZone 对象实例作为参数,我们可以通过 Temporal.TimZone 对象来指定时区,例如传递 Asia/Shanghai 来获取中国时区:

console.log(
  "With time zone: ",
  currentTime
    .toZonedDateTimeISO(new Temporal.TimeZone("Asia/Shanghai"))
    .toString()
);

小结

好了,这个就是 JavaScript Temporal API 的简介和 6 中常见用法,你学会了吗?如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

JavaScript Temporal 日期 API 的 6 种常见用法

二分查找法是一个在已经有序的数组里边查找具体某个值的算法。

算法原理

在查找过程中,需要先找到数组中间位置的值,如果相等,就找到了相对应的元素,并返回它的索引。如果中间值小于要查找的值,就会把左半部分省去,再从右侧开始搜索,同样的找到右侧部分的中间元素,再判断是否相等。 如果大于的话,就去查找左半部分,利用同样的算法,直到找到对应的值。 需要注意的是,要查找的数组必须是已经排好序的,否则无法判断该舍去哪半部分元素。

JavaScript 实现

好,我们接下来看一下二分查找在 javascript 中的实现方式。 首先定义二分查找函数,接收要查找的数组作为第一个参数,然后要查找的值作为第二个参数:

function binarySearch(arr, value) {}

之后我们在函数中定义 start 保存起始索引,定义 end 保存结束索引,根据这两个值我们来计算中间索引,获取数组中间的值:

function binarySearch(arr, value) {
  let start = 0;
  let end = arr.length - 1;
  let middle = Math.floor((start + end) / 2);
}

这个 middle 我们直接使用 Math.floor((start + end) / 2),这样当数组元素有偶数个时,数组可能有两个中间值,我们把靠左的那一个作为中间值。 接下来使用 while 循环反复查找中间值,判断条件是:如果数组的中间值不等于要查找的值,并且 start <= end,就一直查找。也就是说,循环会在找到对应的值后停止,或者这个值不存在,也会停止循环:

function binarySearch(arr, value) {
  let start = 0;
  let end = arr.length - 1;
  let middle = Math.floor((start + end) / 2);

  while (arr[middle] !== value && start <= end) {}
}

之后在循环里面判断,如果要查找的值小于数组的中间值,就去搜索左半部分。 end 就等于 middle - 1。这样就把结束索引定位在了左半边,如果大于的话,就把 start 设为 middle + 1,这样就把要查找的数组定义到了右半边,最后重新计算 middle 的值,找到剩下的这一半数组的中间值:

function binarySearch(arr, value) {
  let start = 0;
  let end = arr.length - 1;
  let middle = Math.floor((start + end) / 2);

  while (arr[middle] !== value && start <= end) {
    if (value < arr[middle]) {
      end = middle - 1;
    } else {
      start = middle + 1;
    }
    middle = Math.floor((start + end) / 2);
  }
}

在循环结束之后,我们需要判断是不是找到了这个值,因为还有可能是没找到这个值而退出了循环。判断 arr[middle] 的值是不是等于要查找的值,如果是,就返回 middle ,也就是这个值的索引。那么如果不是,就返回 -1,表示没有找到:

function binarySearch(arr, value) {
  let start = 0;
  let end = arr.length - 1;
  let middle = Math.floor((start + end) / 2);

  while (arr[middle] !== value && start <= end) {
    if (value < arr[middle]) {
      end = middle - 1;
    } else {
      start = middle + 1;
    }
    middle = Math.floor((start + end) / 2);
  }
  return arr[middle] === value ? middle : -1;
}

我们来测试一下,定义一个数组,包含 9 个元素,然后我们来查找 7。运行一下可以看到它找到了 7 这个元素,索引是 6。如果我们修改一下,查找一个不存在的值,例如 10 ,那么它就返回 -1:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(binarySearch(arr, 7)); // 6
console.log(binarySearch(arr, 10)); // -1

小结

好了,这个就是二分查找算法的原理和实现方法。如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

二分查找算法

我们知道 CSS background 是用来设置元素背景的,但是它的使用上会有很多细节需要注意,否则就会出错。

CSS background 是一系列属性的合集,通过这一个属性,可以对背景进行多种操作,我们接下来看一些它的用法。

移动背景

首先是移动背景。当背景图片比较大时,我们可以对背景进行偏移来展示需要的部分。可以分别设置水平方向偏移和垂直方向偏移,单位可以是任何合法的 CSS 单位,例如像素、百分比、等等:

background: url("./background.webp") -300px -500px;

移动背景也可以利用关键字,可以设置为 top、bottom、left、right 和 center,分别是基于背景的顶部、底部、左侧、右侧对齐和居中对齐:

background: url("./background.webp") center;

或者也可以使用关键字加偏移量这种形式指定四个值。例如这里我们可以设置为距离右侧 -50px,距离底部 -100px:

background: url("./background.webp") right -50px bottom -100px;

移动背景也可以用来展示精灵图中的图标。

背景滚动

通过 background 也可以设置背景的滚动行为。当元素的内容超出容器而出现滚动条时,控制背景是否跟随滚动条进行移动。设置为 local,那么背景就会根据滚动条的滚动而滚动:

background: url("./background.webp") local;

如果设置为 scroll 或者是 fixed ,那么背景就会固定在一个位置。不同的是,Scroll 会相对于元素本身进行定位,而 fixed 是相对于 viewport,也就是浏览器进行定位的:

background: url("./background.webp") scroll;
background: url("./background.webp") fixed;

渐变背景

通过 background 也可以设置渐变背景,利用 linear-gradient 函数设置线性渐变:

background: linear-gradient(90deg, hsl(0deg, 0%, 0%), hsl(0deg, 0%, 70%));

背景尺寸

Background 还可以对背景图片进行缩放。就像是普通的 img 标签那样,可以设置为 cover、contain 或者是某个具体的缩放百分比。但是这个使用上会有一个注意事项,就是设置尺寸的属性,必须跟在设置背景偏移的属性后边,然后这两个属性使用 / 分开,例如我们这个背景设置了基于顶部对齐,然后设置缩放为 cover,这样的话,背景就会缩小到能够填满整个容器,并截掉底下超出的部分:

background: url("./background.webp") top/cover;

设置多个背景

CSS 的背景也可以设置多个。利用这样的特性我们可以对图片加上一个遮罩,或者添加多个层叠的效果。例如这里使用了一个线性渐变,设置为比较深的颜色,并给颜色加上了透明度,然后在底下加载一个背景图片,这样就对背景加上了一个深颜色的渐变的遮罩层:

background: linear-gradient(
    90deg,
    hsl(0deg, 0%, 0%, 0.7),
    hsl(0deg, 0%, 70%, 0.7)
  ), url("./background.webp") center/cover;

好了,这个就是 CSS Background 属性的使用方式以及注意事项。如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

CSS Background 属性的 5 种用法

最新博客 

10 个 CSS 1 行代码技巧

张旭乾

在前端开发过程中,利用 CSS 一行代码技巧可以解决一些常见问题,提高编码效率,使样式设置更加简洁。本文将分享 10 个 CSS 一行代码技巧。

1. 居中对齐

居中对齐是 CSS 中的常见需求。通过一行 CSS 代码,可以实现元素的水平垂直居中。

display: grid; place-items: center;

12个 JavaScript 一行代码技巧

张旭乾

JavaScript 是一种功能强大的编程语言,我们可以通过使用一行代码实现很多功能。在日常开发中,我们经常会遇到需要快速实现某些功能的情况。下面让我们一起看看 12个有用的 JavaScript 一行代码技巧。

1. 生成随机颜色

JavaScript 中可以通过以下方式快速生成一个随机颜色:

let randomColor = "#" + Math.floor(Math.random()*16777215).toString(16);

这行代码会生成一个六位的十六进制数,可以用作 CSS 颜色值。

2. 获取 URL 参数

如果你想获取 URL 参数,可以通过以下一行代码实现:

Vite 和 Webpack 的比较与区别

张旭乾

前言

在现代前端开发中,打包工具已经成为不可或缺的一部分。它们可以将多个文件合并为一个或多个文件,使前端应用的加载和运行速度更快。目前,Vite 和 Webpack 是最受欢迎的打包工具之一。本文将介绍它们之间的比较和区别,以帮助你选择适合自己项目的工具。

什么是 Vite?

Vite 是一种新型的前端构建工具,旨在提高开发过程中的开发体验和构建速度。它支持所有的现代前端框架,如 Vue、React、Angular等,以及原生的 HTML/CSS/JS 应用。

Vite 的核心理念是“快速开发”。它采用了一种新的开发方式,即在开发过程中不需要事先打包应用程序,而是在应用程序运行时即时编译和构建。这种方式使得开发者可以更快地编写代码,而无需等待长时间的编译和构建过程。

什么是 Webpack?

Webpack 是一个广泛使用的打包工具,为现代 Web 应用程序提供了强大的静态资源管理功能。Webpack 可以将多个文件打包成一个或多个文件,并使它们在浏览器中快速加载。Webpack 是一个高度可配置的工具,因此可以根据项目的需要进行定制。

Webpack 的核心理念是“模块化”。它支持使用各种语言和框架编写模块,并将它们打包成可在浏览器中使用的 JavaScript 文件。Webpack 还提供了强大的插件系统,可以扩展其功能。

如何使用 JSX 编写 React 组件

张旭乾

在 React 中,我们通常使用 JSX 来编写组件。JSX 是一种类似 HTML 的语法,它可以帮助我们更方便地编写组件,并且可以使代码更加易读和易于维护。在本文中,我们将介绍如何使用 JSX 编写 React 组件,并且将介绍 JSX 与 HTML 的区别、渲染列表、条件渲染、注册事件和传递 Props 相关知识。

React 框架简介

React 是一个由 Facebook 开发的用于构建用户界面的 JavaScript 库。它采用组件化的开发模式,使得开发者可以将一个 Web 应用程序拆分成多个组件,每个组件负责不同的功能,从而使得开发、测试和维护变得更加容易。

React 的主要特点是高效、可重用和灵活。它采用了 Virtual DOM 技术,可以最小化 DOM 操作,从而提高性能。另外,React 还拥有丰富的生态系统,包括 React Router、Redux、React Native 等等,可以帮助开发者更加高效地构建 Web 应用程序、移动应用程序和桌面应用程序等等。

10 种方法使用 CSS 水平居中一个元素

张旭乾

我们在前端开发中,经常会有居中某个元素的需求。因为 CSS 对于居中的方式有多种多样,在不同场景下有不同的效果,需要特别记住它们的应用场景才能够正常的居中元素。那么这篇文章,我们就看一下在 CSS 中,水平居中一个元素的不同方法和技巧,帮助你在前端开发中更加游刃有余。

使用 text-align 居中行内元素

text-align 不仅可以居中文本,还可以居中行内元素(如文本或图片)。把父元素的 'text-align' 设置为 center,然后把子元素的 display 属性设置为 inline 就可以了。

.parent-element {
text-align: center;
}

配置 Vite alias 别名导入,避免冗长的相对路径

张旭乾

在开发大型 Vue 项目的时候,有的组件会嵌套的很深,如果需要引入外层其他目录的组件,需要编写很长一段相对路径。假如有如下组件目录结构:

/Layout/Header/NavBar.vue
/Base/BaseLink.vue

如果在 NavBar 中引入 BaseLink 组件,需要使用下面的路径进行导入:

NavBar.vue
<script>
import BaseLink from '../../Base/BaseLink.vue';
</script>

这里使用了两个 ../ 才访问到 Base 目录,如果这个时候,我们给 ../../ 设置一个别名,例如 @,那么就可以使导入路径更简洁,变成:

@/Base/BaseLink.vue

Vite 支持设置路径别名,来支持这种形式的导入。

Vue3 表单提交事件处理

张旭乾

这篇文章我们看一下对于表单整体的提交事件应该如何处理,并引入一个事件修饰符的概念。 我们继续使用上篇文章的项目示例,这里我把 html 中,class 为 form 的 div 改成了 form 元素,CSS 的样式也作了对应的调整。

<div class="form">
->
<form></form>
</div>

现在,我们在 form 元素的结束标签之前,添加一个 button 按钮元素,作为表单的提交按钮,点击它表单就会提交。

<textarea id="intro" rows="10" v-model="intro"></textarea>
<!-- 在这里添加 -->
<button type="submit">提交</button>

Vue 3 常见表单控件事件处理

张旭乾

上篇文章我们学习了如何使用 v-model 绑定 input 输入框和 data 中的属性,这篇文章我们来看一下其它表单控件的绑定方法。

示例

这里示例大体的样式和结构我已经写好了,你可以从视频里附带的源代码示例里,直接编写代码。用户名这个我们上篇文章介绍了,这个就先不管它了。

单选按钮

我们先看一下单选框的数据绑定。假如我们让用户选择一下性别,男和女。我们在 HTML 模板中:

  • 定义一个 type 为 radio 的 input,name 设置为 gender,这里的 name 相同的 radio 单选框才能形成一个单选按钮组,这样选择是互斥的。
  • value 设置为 male,表示男性,这个是单选按钮选中之后,实际获取到的值。这里之所以用英文,是遵循了软件开发的一些最佳实践,方便后端程序或数据库,用枚举的形式存储数据,而枚举的属性名一般用英文。
  • 在 input 后面,我们用一个 span 元素显示给用户看的值,这里写上『男』。
  • 这里用 v-model 绑定一个名为 gender 的属性,这个我们稍后再在 data 里定义。

Vue 3 表单输入控件数据处理

张旭乾

在 Vue 中处理表单输入数据处理有两种方式,一种是使用传统的事件监听方式,另一种是使用 v-model,这篇文章我们看一下如何使用事件监听的方式处理表单输入数据。

input 事件处理

要监听 input 输入框的事件,我们使用 @input 事件,在用户输入每个字符的时候,都会触发这个事件。

<input id="username" type="text" @input="handleInput" />

我们可以在事件处理函数中,直接访问到 event 这个 javascript 原生事件对象,这样可以像普通 js 一样,使用 event.target.value 来访问输入框的值:

Vue methods 方法和 watch 监听器的区别

张旭乾

这篇文章我们看一下方法和 watch 监听器之间的区别。

Methods 方法和 Watch 监听器的区别

MethodsWatch 之间其实并没有什么太大的可比性,只是当 methods 方法作为 computed 计算属性那样使用时,那么它和 Watch 监听器之间的区别和计算属性跟监听器中间的区别就很类似了,当在 HTML 模板中调用方法时,会把方法的返回值计算出来并显示:

<ul>
<li v-for="vueBlog in getVueBlogs()">{{ vueBlog }}</li>
</ul>

methods: {
getVueBlogs() {
return this.blogPosts.filter((blog) => blog.includes("Vue"));
},
},