跳到主要内容位置

峰华前端工程师的生活之道

这里记录我对程序员生活的观察和总结:如何调节身心,减轻压力,恢复能量。技术之外,不能丢掉生活,照顾好自己,才能在追求技术、财务和职业发展的道路上走得更远。
“峰华前端工程师”账号创作者 / 《JavaScript 基础语法详解》作者
在这些平台找到我

最新视频

内容加载中…

什么是 Node.js

Node.js 是一个可以运行在服务器端的 JavaScript 运行环境,并且是开源和跨平台的。我们在网页中使用的 JavaScript 是浏览器提供的运行环境,是运行在客户端,也就是用户的电脑上的。

Node.js 则把 Chrome 的 V8 JS 引擎独立了出来,脱离了浏览器,这样就可以使用 JavaScript 开发服务端应用了。

全栈开发

因为 Node.js 同样使用 JavaScript 语言,那么我们可以只学习一种语言,就可以开发多端应用,变成全栈工程师,减少了学习成本。

热门程度

在 stackoverflow 2024 年的开发者调查中显示,Node.js 是最热门的服务端开发技术:

而 JavaScript 则是适用人数最多的语言:

并且 Node.js Foundation 报告世界 500 强中有 98% 都或多或少的使用了 Node.js。

谁、何时开发了 Node.js

Node.js 是由 Ryan Dahl 开发的,于 2009 年发布。开发 Node.js 目的是为了解决 Apache 这个热门服务器在处理大量并发连接时的性能,同时减少资源消耗。

Event Driven 架构

Node.js 采用了事件驱动,非阻塞 I/O 的架构模式,并且使用单线程结合 Event Loop 的方式来执行代码。不需要维护线程池,减少了内存的消耗,提高了性能,适合实时应用的开发,例如股票交易应用,也适合大数据应用的开发。

模块化

Node.js 对 JS 本身的一大影响就是支持了模块化,把处理不同逻辑的代码放到不同的文件中,形成模块,然后在其他文件中可以进行引入并使用,这样就方便的封装和复用。

// math.mjs
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}
// app.mjs
import { add, subtract } from './math.mjs';

console.log(add(5, 3)); // 输出: 8
console.log(subtract(10, 4)); // 输出: 6

API

在使用客户端 JavaScript 时,浏览器提供了 Window、Document 等 API 让我们能够操作浏览器窗口和网页等,而 Node.js 也提供了一组 API 让我们更方便的编写服务端应用,例如 File 文件读写,HTTP 服务器,OS 操作系统工具,等等。

示例

要使用 Node.js,我们可以在它的官网下载对应操作系统的安装包,它分为稳定版和最新版,稳定版更新周期稍慢,官方支持周期长,最新版更新周期快能体验新的特性,可以根据自己的需求进行下载。

这里,我们有一个简单的 Node.js HTTP API 使用示例,用于处理一个 GET 请求,并返回响应结果:

  • 代码从 node:http 模块中导入了 createServer 函数来创建服务器。
  • 之后调用 createServer,在里边处理了请求和响应,无论什么请求都返回 200 响应码和 Hello World 文本。
  • 最后调用 createServer 返回的实例中的 listen 方法,来启动服务器并监听本地 3000 端口。
// server.mjs
import { createServer } from 'node:http';

const server = createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World!\n');
});

// 启动服务器到本地 3000 端口
server.listen(3000, '127.0.0.1', () => {
  console.log('Listening on 127.0.0.1:3000');
});

// 运行 `node server.mjs` 来启动服务器

使用 node server.mjs 运行服务器之后,在浏览器访问 http://localhost:3000就可以看到返回的 Hello World!结果了。

小结

好了,这个就是 Node.js 的介绍,你学会了吗?如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

3 分钟了解 Node.js

内容加载中…

我们在使用 React 处理表单给服务器发送数据的时候,需要分别手动处理 Loading 加载,error 错误,和服务器请求,这样的 state 比较分散,代码不容易维护:

function CommentForm() {
  const [comments, setComments] = useState([]);
  const [isPending, setIsPending] = useState(false);
  const [error, setError] = useState(null);

  async function handleFormSubmit(e) {
    e.preventDefault();
    setIsPending(true);
    const formData = new FormData(e.target);
    const comment = formData.get("comment");
    try {
      const result = await submitComment(comment);
      setComments((comments) => [...comments, result]);
      setIsPending(false);
    } catch (error) {
      setError(error);
    }
  }

React 19 出了一个新 Hooks, useActionState 可以让我们集中管理 loading、error 和服务器的请求,它:

  • 接收一个 async 的函数作为参数,在里边我们可以编写表单的提交操作。
  • 这个函数又接收两个参数,一个是表单之前的状态,一个是React 帮我们包装好的表单对象,基于浏览器的 FormData API,它包含各个表单控件的用户输入。
  • 之后在里边我们可以访问表单数据并提交到服务器。
  • 函数的返回值会作为 useActionState 的返回值之一,一般用于返回错误。
  • 最后 useActionState 还接收第二个参数,作为表单状态的初始值。
  • useActionState 会返回一个数组,它里边有我们 async 函数的返回值,用于给表单 action 属性绑定的事件处理函数,还有一个加载状态。

这样定义好之后,我们就可以在 JSX 中使用了:

const [error, submitCommentAction, isPending] = useActionState(
  async (previousState, formData) => {
    const comment = formData.get("comment");
    const result = await submitComment(comment);
    setComments((comments) => [...comments, result]);
    return null;
  },
  null
);

<form action={submitCommentAction}>
  <textarea
    name="comment"
    placeholder="写个评论吧..."
    cols={40}
    rows={4}
  />
  <button type="submit" disabled={isPending}>
    发布评论
  </button>
</form>
{error && <p className="error">{error}</p>}
<ul className="comments-list">
  {comments.map((comment, index) => (
    <li key={index}>
      <p>评论:{comment}</p>
    </li>
  ))}
</ul>

在表单提交后,React 现在还会帮我们自动清空表单控件的里的输入,只要这些输入框是非受控组件。

乐观 UI

另外,表单处理还有另外一种常见的需求,就是乐观 UI,在提交数据后,先更新 UI,随后再处理服务器数据,这样能先给用户反馈,提高程序响应性。

React 19 提供了 useOptimistic Hooks 来实现乐观 UI,我们可以:

  • 使用它包装一下表单本身的状态。
  • 它会返回和 useState 类似的两个结果,一个是乐观状态值,一个是修改乐观状态的函数。
  • 之后我们在表单处理函数里,调用修改乐观状态的函数就可以了,可以在发送服务器数据之前。
  • 最后在 JSX 中,使用乐观数据展示就可以了。
const [optimisticComments, setOptimisticComents] = useOptimistic(comments);

const [error, submitCommentAction, isPending] = useActionState(
    async (previousState, formData) => {
      const comment = formData.get("comment");
      setOptimisticComents((optComments) => [...optComments, comment]);
      const result = await submitComment(comment);
      setComments((comments) => [...comments, result]);
      return null;
    },
    null
  );

<ul className="comments-list">
  {optimisticComments.map((comment, index) => (
    <li key={index}>
      <p>评论:{comment}</p>
    </li>
  ))}
</ul>

访问上层表单状态

有时候表单组件的嵌套比较复杂,每个表单控件可能都是一个独立的组件,这样在获取表单状态的时候,需要多次传递 props,很不方便,这个时候 React 19 提供了 useFormStatus hooks,只要是 form 元素下层的组件,都可以访问表单的状态,例如加载状态、数据和错误等等,调用 useFormStatus 会返回包含这些信息的对象,例如我们可以在按钮中访问表单的加载状态,然后根据它来判断是否禁用按钮,之后父组件也不用再传递props了:

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      发布评论
    </button>
  );
}

 <form action={submitCommentAction}>
  <!-- ... -->
  <SubmitButton />
</form>

小结

好了,这个就是 React 19 表单处理的新方式,以及相关的 hooks,你学会了吗?如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

React 19 表单处理新方式-新Hooks

内容加载中…

我们在使用 CSS 实现动画的时候,是以时间为基础的。keyframes 里的百分比,会根据 animation 属性中指定的时间来分配:

.someElement {
  animation: aniamte 1s linear;
}

@keyframes animate {
  0%{}    /* 0s */
  25%{}
  50%{}   /* 1s */
  100%{}  /* 2s */
}

现在,CSS 出了一个新特性,让我们可以按滚动来实现动画,这就是 CSS 滚动驱动动画。

滚动的方式有两种:

  • 一种是以距离最近的父组件的滚动条的滚动,keyframes 百分比根据滚动条的位置计算。
  • 另一种是按元素自身出现的位置来执行动画,就是说元素从底部滚动出来,到上方滚动出去的过程,keyframes 的百分比根据元素位置进行计算。

利用 CSS 滚动驱动动画可以实现:

  • 视差效果
  • 全屏阅读进度
  • 幻灯片
  • reveal 出现动画

配置 CSS 滚动动画

那要使用 CSS 滚动驱动动画,我们需要配置 animation-timeline 属性来指定滚动方式,使用 scroll() 函数可以按最近父元素的滚动条实现动画。scroll() 函数接收两个参数:

  • 第一个参数是根据哪个元素的滚动执行动画,它可以取 3 个值:
    • neareast - 最近的有滚动条的父元素,这个是默认值
    • root - 根元素的滚动条,也就是最顶级的
    • self - 元素自身的滚动条
  • 第二个参数指定方向,可以指定:
    • block - 块级方向,这是默认的
    • inline - 行级方向
    • y - 纵向
    • x - 横向
    • 所以有 block 和 inline 是因为当文字是横向书写的,那么 block 相当于 y,inline 相当与 x,而竖写的文字则 block 相当与 x,inline 相当于 y,这样会比较灵活。
animation-timeline: scroll(root block); 
/* nearest, root, self */
/* block inline y x */

然后再定义动画 keyframes 并使用 animation 属性指定动画即可,要注意的是这里 animation 就不能指定时间了:

@keyframes move200 {
  0% {
    translate: 0 0;
  }
  100% {
    translate: 0 -200px;
  }
}

.someElement {
  animation: move200 linear;
}

如果要按元素位置来执行动画,可以使用 view() 函数,view 也接收两个参数:

  • 第一个参数也是指定方向,可以取同样的 block、inline、y和x
  • 第二个参数指定 offset,即元素滚动执行动画的区间,不指定则是整个滚动区域,可以指定两个值指定开始和结束区域。
animation-timeline: view(block 50% 10%);

使用 view() 实现动画,还可以指定 animation-range 属性,来定义元素自身出现在什么位置开始动画,在什么位置结束动画,它可以取的值有:

  • entry - 元素上边界开始进入到滚动的可视区域中到完全进入结束
  • exit - 元素开始离开滚动可视区域到完全离开结束
  • entry-crossing - 元素从进入开始到完全进入之间
  • exit-crossing - 元素离开开始到完全离开之间
  • cover - 元素上边界进入到滚动区域,到下边界完全离开。
  • contain - 元素完全在滚动区域之中。

可以分别指定开始和结束区间,每个关键字后面可以跟上百分比偏移,例如:animation-range: entry 0% entry 100% 表示元素进入动画,从上边界进入开始,到下边界与浏览器底部相交结束,如果开始和结束的关键字相同,偏移为 0% 到 100%,可以只写关键字,例如这里可以简写为 entry:

animation-range: entry 0% entry 100%
animation-range: entry

这张图描述了 animation-range 各个值的范围,这些关键字指定的区域会有重合,可以看情况选择最贴近动画执行过程的。

兼容性

目前 CSS 滚动驱动动画在 Chrome 和 Edge 浏览器中实现了,其他浏览器兼容情况可以参考 can i use 网站:

示例

我们来看两个具体的例子,第一个例子实现了两个图片视差滚动的效果,使用 scroll() 根据根元素的滚动执行动画,第一个图片的动画使用了 translate () 属性来加速它的上移。

第二个例子利用了 view() 实现了图片的进入特效,在它进入时放大、透明度设置为 0,然后完全进入并到 50% 的位置时还原为正常状态,这样就有了一个从放大到缩小的动画。

小结

好了,这个就是 CSS 滚动驱动动画的介绍,你学会了吗?如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

2 分钟学会纯 CSS 滚动动画:根据滚动条或元素位置执行动画

内容加载中…

HTMX 是一个主打增强 HTML 功能的前端框架,通过给 HTML 添加自定义的属性,让任何元素都可以发送 Ajax 请求,实现 CSS 过渡、Web Socket 和服务端事件响应等常见的 Web 端功能,而不需要我们编写额外的 JS 代码。

这个设计哲学就符合了 HTML 的 Hypertext 特性,也就是使用纯文本的方式来编写网页内容。

htmx 的包体积很小,只有 14k,但用它可以实现常见的前端应用:例如网站、单页应用、实时应用等等。

要使用 htmx,只需要引入它的库即可:

<script src="https://unpkg.com/htmx.org@1.9.10"></script>

发送 HTTP 请求

要发送 HTTP 请求,可以给触发的元素添加 hx-get、hx-post 等属性,分别发送 GET、POST 请求,HTMX 默认会在鼠标点击的时候触发:

<button
  hx-get="https://jsonplaceholder.typicode.com/todos"
  >
  加载 Todos
</button>

之后通过 hx-target 指定要替换的页面元素,一般通过 id 来查找:

<button
  hx-get="https://jsonplaceholder.typicode.com/todos"
  hx-target="#todo-list"
>

更新页面

之后我们可以定义一个要显示请求数据的 HTML 元素,并指定 ID 属性:

<div id="todo-list"></div>

这样服务端返回的数据就会放到这里边。htmx 建议后端直接返回 HTML 内容,这样可以拿来就用。

htmx 默认会把数据放到元素内部,我们可以通过 hx-swap 来指定其它的替换方式,默认是innerHTML:

<button
  hx-get="https://jsonplaceholder.typicode.com/todos"
  hx-target="#todo-list"
  hx-swap="innerHTML"
>

也可以指定其他值:

outerHTML:替换整个元素。

afterBegin: 在元素内部开头插入

beforeBegin:在元素外部上方插入

beforeEnd:在元素内部结尾插入

afterend:在元素外部下方插入

delete: 删除元素

none:不添加内容

加载状态

要显示加载状态,可以给元素设置 htmx-indicator class,htmx 会自动控制它的显示与隐藏:

<button
  hx-get="https://jsonplaceholder.typicode.com/todos"
  hx-target="#todo-list"
  hx-swap="innerHTML"
  >
  加载 Todos <span class="htmx-indicator">...</span>
</button>

自定义事件触发

如果要修改htmx默认的鼠标点击触发事件,可以配置 hx-trigger,例如在鼠标移上去时触发事件

<button
  hx-get="https://jsonplaceholder.typicode.com/todos"
  hx-target="#todo-list"
  hx-swap="innerHTML"
  hx-trigger="mouseenter"
>
  加载 Todos <span class="htmx-indicator">...</span>
</button>

事件 Modifiers

htmx 也支持给事件添加修饰符,配置延迟,或者事件是不是之触发一次:

<button
  hx-get="https://jsonplaceholder.typicode.com/todos"
  hx-target="#todo-list"
  hx-swap="innerHTML"
  hx-trigger="mouseenter delay:1s once"
>
  加载 Todos <span class="htmx-indicator">...</span>
</button>

使用 Extensions(handlebars)

htmx 支持 Extensions 扩展来实现更多功能,如果后端返回 JSON 格式数据,可以利用 client-side-templates 插件,配合 handlebars 模板引擎来渲染:

<script src="https://unpkg.com/htmx.org/dist/ext/client-side-templates.js"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>

通过 script 引入相关的库后,在需要使用插件的元素外层元素中:

  • 添加 hx-ext 属性,值为插件名。
  • 然后配置 handlebars-array-template 值为自定义的模板 id,这个用于处理服务端返回数组 JSON 数据,如果是单个对象,可以把属性名中的 array去掉。
  • 之后使用 template 定义模板,id指定为 handlebars-array-template 中配置的,之后在里边编写模板即可。
  • 最后 id 为 todo-list 的 div 会用模板中的格式进行展示。
<div hx-ext="client-side-templates">
  <button
    hx-get="https://jsonplaceholder.typicode.com/todos"
    hx-target="#todo-list"
    hx-swap="innerHTML"
    handlebars-array-template="todo-list-template"
    hx-trigger="mouseenter delay:1s once"
  >
    加载 Todos <span class="htmx-indicator">...</span>
  </button>
  <div id="todo-list"></div>
  <template id="todo-list-template">
    <ul>
      {{#each this}}
      <li>{{title}}</li>
      {{/each}}
    </ul>
  </template>
</div>

演示

看一下最终演示代码,从 jsonplaceholder 获取 todos 列表后,获取 json 数据,然后通过 handlebars 渲染所有的 todo title 也就是标题属性的内容。

小结

好了,这个就是 htmx 的介绍,你学会了吗?如果有帮助请三连并关注,想学更多的开发知识,可以在评论区留言,感谢观看!

3分钟了解 HTMX