yang 发布的文章

Design is not just what it looks like and feels like.
Design is how it works.

看完苹果今年iPhone 17系列发布会是一整个心动不已,今年亮点还是挺多的,更PRO的PRO系列,牙膏挤爆的基础版。在今年基础版的短板几乎都被补齐了,可谓是近几年最值得买的基础版了。于是果断冲了首发的17,加上国补,价格来到了5499,感觉还是挺划算的。
1.jpg

开箱

苹果一贯的风格,白色盒子,正面印着 iPhone 17 的背面。
微信图片_20251006000705_5_2.jpg
上手给我的第一感觉就是,轻。对比于我的上一代设备iPhone14 Pro来说。

而且整体的质感好了很多,虽说是铝合金,但是我觉得比14的手术级不锈钢摸起来要更舒服,手感要更好hhh。

上手后,我最喜欢的是它的握持感。6.3 英寸大小刚好,单手操作没问题。相比 Pro 版,它没有那么重的相机模组,那么大的deco,所以更轻便。也更适合我。(适合自己的才是最棒的bushi 虽说也没钱上Pro)

而且这个黑色特别讨我喜欢。
微信图片_20251006000711_6_2.jpg
质感超级棒的,而且是17系列唯独的黑色,黑色给我带来的感觉是别的色无法比拟的。高级而神秘的黑色。

细致地说

本次升级比较大的还得是摄像头,前置摄像头升级了 Center Stage 功能。挺牛逼的,从根本上解决了横屏自拍不方便,眼神很奇怪的问题。虽说我不会怎么自拍,所以说升级了其实和我也没啥关系hhhhhh。所以最主要的还是后置摄像头,两颗48mp的镜头,细节锐利,颜色自然。特别的nice,加上16系列以来加入的调色盘,拍出来的效果也并不逊色于前几代的Pro机型。

所以我觉得现在苹果对于基础版的定义,并不算是丐版了,而是真正符合大多数人的日常的使用需求,而Pro则是更Pro,为专业用户而设计的,而Air系列则是在便捷性与性能之间找到了平衡,适合那些既需要一定性能,又不想要过多负担重量的用户。

简单来说,基础版适合入门级用户,Pro系列则为需要顶级性能的专业人士提供了极致体验,而Air则给那些追求轻便和高性价比的用户提供了一个折衷方案。在每个层次上,苹果都在不断优化用户体验,不再是过去那种“基础版省去许多功能”的做法,而是用合理的定价和出色的设计,让每一个用户都能找到合适的产品。

This is not a component library. It is how you build your component library.

Shadcn/ui 像是摆在你面前的一块原石,没有直接打磨成品,而是递给你一把刻刀。

这也意味着,它并不是一个现成可用的“库”,而是一套让你自己动手打造组件体系的组件库。

01 它的“非库”特性

Shadcn/ui 给你的不是封装好的黑盒,而是组件源码。你复制到本地后,它就是你项目的一部分,与其他代码无异。

那好处显而易见:

  • 完全可控:逻辑、样式、交互你都能直接改。
  • 高度可定制:从 design token 到动效,你都可以按团队风格塑形。
  • 设计统一:早期就能与项目的 UI 语言绑定,不必事后修补。

但它的代价也很真实:维护责任全在你。

02 从“拿来”到“养成”

初用 Shadcn/ui 时,只是把它当成“官方样式集”仅此而已,复制粘贴后就直接用,这种用法短期高效,而对于长期而言失控。(((

而我的建议是,第一步先做基础设计层:

定义 design token

在 tailwind.config.ts 里声明颜色、间距、圆角、阴影的变量,组件全部吃变量,不直接写死值。

二次封装组件

官方的 Button、Card 等不要直接暴露,先包一层自己的版本,这样全局设计改动时不会满世界找文件。

03 更新与演进

Shadcn/ui 官方的组件实现会悄悄更新,但你的项目本地文件不会自动跟上。而想要最新的好东西?得自己动手去更新。

  • 按需拉取 不搞全量同步,只更新我在用的那几个组件,省得引入一堆无关 diff。
  • 手动对比 用 Git 把官方最新版本和我本地的实现做个 diff,看哪些是性能优化、哪些是 API 变更,再决定“要不要搬”。
  • 设个更新日 想起来了就自己检查下....(我很懒就是了)

这样一来,更新变成了一种可控的节奏,而不是头脑一热的全盘替换。然后发现自己搞得乱七八糟。

04 折腾的意义

Shadcn/ui 吸引我的地方,不是它有多“全能”,而是它逼你动手。
不像那些“拎包入住”的 UI 库,给你一套现成的风格,然后你只能在框框里改一点边角。它更像是说:

喏,材料和工具给你了,房子自己盖。

这种模式一开始挺累的——要自己调颜色、改间距、封装逻辑……

但慢慢地,你会发现这个过程其实很爽。因为最后产出的,不是“某某库的组件”,而是你自己项目的语言。

这篇文章主要来源于我的个人理解,不一定准确,但是我是这样用的(

1. Next.js App Router

提到Next.js的主要组成的的话,我觉得就是路由系统了。Next.js路由写起来很简单,而且很实用,先从文件系统路由来说,其次为动态的路由。

文件系统路由

  • src/app/文件夹里创建文件夹,在Next.js中即算为路由
  • page.tsx文件就是这个路由的页面
  • 比如src/app/about/page.tsx就是/about页面

动态路由

  • 用方括号[xxx]来创建动态的路由,比如/blog/[cid]/page.tsx即为一个动态的路由
  • 然后呢在组件里用useParams()可以获取到这个参数
  • 此界面的实现就是这样的:/blog/123中的123就是所获取到的cid

2. React Hooks

React Hooks 是一种函数式组件的增强机制,它允许你在不编写类组件的情况下使用 React 的特性。主要的 Hooks 包括 useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, 和 useImperativeHandle 等。这些 Hooks 提供了访问 React 特性的方式。

以下是一些我用到的React Hooks的具体实现

useState - 管理状态

useState Hook 允许你在函数组件中使用局部状态。它会返回一个状态值和更新该状态值的函数。
const [loading, setLoading] = useState(true) // 此处即表示加载状态
const [comments, setComments] = useState([]) // 评论列表

useEffect - 执行副作用操作

useEffect Hook,允许你将组件与外部系统同步(如数据获取、订阅管理、DOM 操作等)。它在每次渲染后都会执行。
useEffect(() => {
  // 页面加载时获取数据
  fetchData()
}, []) // 空数组表示只在组件挂载时执行一次

useRef - 获取DOM元素

用于创建对 DOM 元素或值的引用,可以在渲染之间保持状态。
const textareaRef = useRef(null)
// 可以直接操作textarea元素,比如设置光标位置

3. TypeScript

TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。

TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。

定义接口的一个小例子

interface Post {
  id: number
  title: string
  content: string
}

这段代码的意思是:

定义了一个叫 Post 的接口(interface),它描述了一个“帖子”的结构,里面有三个属性:

  • id:编号,是一个数字(number)
  • title:标题,是一个字符串(string)
  • content:内容,也是字符串(string)

这个接口可以用来规范对象的形状,比如下面这样:

const post: Post = {
  id: 1,
  title: "标题",
  content: "这是内容"
}

这样写,TypeScript会帮你检查这个对象是否符合 Post 的结构。

所以说TypeScript的优势就在这里

  • 写代码的时候有智能提示,能提前发现错误,不用等到运行时才知道。
  • 代码会更容易维护。

4. 组件化管理

复杂的页面的往往会出现重复的代码段,在Next.js中我们可以把这些重复的代码段拆分成单个小组件,然后直接引用,这样代码不仅更好管理,代码更加的清晰:

组件拆分的思路
例如我每个页面都有顶栏(navbar),如果每个页面都写重复的navbar代码,改起来便很麻烦。所以就将其拆分为navbar.tsx然后存放于components之中即可。然后页面头部引用该文件。

import { Navbar } from "@/components/navbar"

5.Props传递

interface CommentProps {
  comment: Comment
  onReply: (author: string) => void
}

function CommentItem({ comment, onReply }: CommentProps) {
  // 组件内容
}

这段代码演示了 在 React(Next.js)中通过 Props 向组件传递数据和函数 的方式。

1. interface CommentProps

定义了传给 CommentItem 组件的参数类型(也叫 props)。

  • comment:是一个 Comment 类型的对象,表示一条评论(例如包含作者、内容、时间等)。
  • onReply:是一个函数,接收一个字符串(作者名),用于点击“回复”按钮时执行相应操作。

2. function CommentItem({ comment, onReply }: CommentProps)

这是一个 React 函数组件。

使用了解构语法,从传入的 props 中直接取出 comment 和 onReply。

这样可以在组件内部使用 comment 数据来显示评论内容,也可以在用户点击“回复”时调用 onReply() 来触发父组件的处理逻辑。

6. 数据处理

这里以递归处理评论树来说

在评论系统中,常常会有“评论回复评论”的结构,比如:

- 评论 A(id: 1)
  - 回复 A1(parent: 1)
    - 回复 A1-1(parent: A1)
  - 回复 A2(parent: 1)、

这样的结构是树形的,每条评论通过 parent 字段来指向它的上一级评论。

function getAllReplies(comments, parentId) {
  const replies = []
  comments.forEach(comment => {
    if (comment.parent === parentId) {
      replies.push(comment)
      // 递归查找子评论
      replies.push(...getAllReplies(comments, comment.id))
    }
  })
  return replies
}

这段代码便是找到某个评论(parentId)下的所有“直接和间接”子评论,并把它们放在一个数组里返回。
假设我们调用:

getAllReplies(comments, 1)

意思是找出「ID 为 1 的评论」下面的所有子评论。

那函数怎么做的呢?

  • 1.遍历每条评论
  • 2.如果某条评论的 parent === 1(说明它是 ID 为 1 的直接回复)
  • 3.把它加进 replies 结果数组里
  • 4.然后递归调用自己:去找这条子评论的“子评论”
  • 5.一直递归下去,直到找不到更多子评论为止
  • 6.最后返回一个包含所有层级子评论的扁平数组

7. 表单处理

React的表单处理有自己的套路。它处理表单和原生HTML有些不同,核心思想是:用组件的状态(state)来控制输入框的值,这就是“受控组件”的概念。

受控组件

const [form, setForm] = useState({
  name: '',
  email: '',
  message: ''
})

<input 
  value={form.name}
  onChange={e => setForm({...form, name: e.target.value})}
/>

注意

  • useState 是 React 的一个 Hook,用来声明状态。 form
  • 是一个对象,包含三个字段:name、email、message,分别对应输入框的值。
  • setForm 是更新 form 的函数。

上半段代码表示:我们要用 form 这个 state 来控制输入框的值,每个输入框的内容都保存在 form 里。
而下半段即是把所有表单数据都保存在 state 里,我们可以随时查看、验证、提交。

表单提交

const handleSubmit = async (e) => {
  e.preventDefault() // 阻止默认提交行为
  try {
    await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify(form)
    })
    alert('提交成功!')
  } catch (error) {
    alert('提交失败!')
  }
}

在这段代码中呢

  • handleSubmit 是你绑定在
    上的提交事件处理函数。
  • e.preventDefault():阻止表单默认的提交行为(不刷新页面)。
  • fetch('/api/submit', { ... }):用 JavaScript 发起 POST 请求,把表单数据(form)发送到后端。
  • JSON.stringify(form):把表单对象转成 JSON 字符串。 alert('提交成功!'):提交成功后弹出提示。
  • 如果出错(例如网络问题或服务器异常),就会进入 catch 显示“提交失败”。

8. 对性能进行优化

动态导入是指在用户访问页面时,根据需要按需加载组件,而不是在初始页面加载时将所有组件都打包。这种方式可以显著优化性能和用户体验。

// 组件懒加载,减少首屏加载时间
const MarkdownEditor = dynamic(() => import('./MarkdownEditor'), {
  ssr: false // 不在服务端渲染
})

同时还要避免不必要的重新渲染

  • 比如合理使用useEffect的依赖数组
  • 还有事件处理函数记得清理,避免内存泄漏

这些是我在学习Next.js/React的一些笔记与总结,不一定完全对awa(欢迎指正)。也算是一些记录,之后或许会继续更新(

这篇文章发自我重构的后台.
(基于Next.js React Shadcn/ui Typecho XML-RPC接口)
想写点东西,但总是不知道写点啥好,只能写点垃圾小文章了🌝
IMG_0835.png

🧸 小时候的快乐很简单

不知不觉,我好像已经不能轻轻松松的快乐了… 夹在“已经不是小孩”和“还不算大人”之间,有点尴尬,也有点累。
有时候真的会怀疑:长大以后还能快乐吗?还是说,我们只是学会了怎么不去想太多?

长大好像就是这样:我们开始学会权衡、计划、克制,情绪也变得更复杂。
快乐变成了一种需要安排、需要时间、需要代价的事。

🍬 Thinking.

我知道,未来还有很多更现实的事情要面对,
我们不能一直当孩子,也不能永远活在回忆里。

但我也想相信,就算长大了,也可以偶尔任性一下,偶尔停下来抱抱小时候的自己。

时而保持一颗童心


那个小小的你,从未离开。