重逢React

前言#

react是我非常喜欢的一个前端框架。我打算继续学习一下相关内容,并新做一个自己的博客?

创建一个react项目#

参见react教程中的 启动一个新的react项目。我个人使用的是其中的next.js框架。

react基础#

这一部分我是参照着next.js教程中的React部分学习的。每一章的内容都很简单。基本就是一两句话,因此我就只写一些简单的记录。

  1. 什么是react和next.js:react是构建web应用的一个框架,而next.js则是方便你使用react的一个框架。一个web应用包括了很多方面,比如ui、路由、数据存储、渲染界面等等,react框架就为这些方面提供了很多支持。进一步的,next.js则是react上的一个框架,设计了很多便于开发者使用react的功能。
  2. 网页结构:网页结构决定于dom,通过dom我们可以实现对任意元素的访问和操作,从而动态改变网页结果。
  3. 原始js可以动态改变dom、渲染结果:dom是动态的网页结构,会随着网页内容的变化而变化。使用原始js对dom的改变需要开发者详尽地编写修改逻辑,这种编程方式称为命令式编程(imperative)。而使用react库则可以跳过这些细节,只需要告诉react,我们的希望文档做出什么样的改变,react就可以帮助我们完成。这种编程方式称为声明式编程(declarative programming)。
    声明式编程不关心底层逻辑是如何实现的,只关心最终的结果是怎么样的。这样就将功能实现转交给了底层模块,提高了可复用性。而指令式编程则集成化了实现代码,使模块更紧凑。

    举例来说,如果你想吃锅包肉,你可以按照菜谱,一步一步地做,这是imperative programming。或者,你可以下馆子,给服务员说,我要吃锅包,然后就可以等锅包肉上桌了。这就是declaractive programming。

  4. 使用react:说到底,react只是js代码,因此如果想在工程中使用react库,可以有两种方法。一种就是直接在html代码中使用<script></script>标签动态链接js代码。另一种之后我们会说到。我们先通过第一种方法来简单看看react是如何工作的。
    假如说我们有如下原始三件套:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <html>
    <body>
    <div id="app"></div>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script> <!-- react库引入-->
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <!-- react库引入 -->
    <script type="text/javascript">
    const app = document.getElementById('app');
    const header = document.createElement('h1');
    const text = 'Develop. Preview. Ship.';
    const headerContent = document.createTextNode(text);
    header.appendChild(headerContent);
    app.appendChild(header);
    </script><!-- 使用js修改文档的逻辑 -->
    </body>
    </html>
    可以看到内含一段使用js来修改文档内容的代码,我们如果想用react来替换这一段js的话,可以用下面这段来稍作修改
    1
    2
    3
    4
    5
    <script>
    const app = document.getElementById('app');
    const root = ReactDOM.createRoot(app);
    root.render(<h1>Develop. Preview. Ship.</h1>);
    </script>
    首先还是获得我们希望的添加元素的对象,然后通过reactdom.createRoot(app)来告诉react,我们要往这个元素上添加新的元素,最后则将要添加的内容,传给成员函数来完成渲染。
    可以看到,这种代码非常的简洁易读。不过,如果你真的让浏览器解析修改后的html代码,你会得到一个报错。这是因为,js不支持 html语法解析,或者说,js中 不能写html代码。因此这种传参方式是错误的。
    这种允许你编写含有html in js语法的js代码的代码格式,称之为 jsx代码格式。不仅如此,你还可以在html中写js代码,只需要给js代码加上{}即可。总而言之,jsx格式完全只是为了便于描述ui之间的交互方式而做的,因此你不必担心是否需要额外了解这些内容。
    jsx代码会在构建阶段被转译器转换为标准的js代码,从而正常执行。babel就是这样的一个jsx转译器。它还是一个js库,因而可以按照之前js动态链接的方式链接该js库。为了让babel知道需要转译什么样的脚本,开发者需要将对应的script标签的type属性改为"text/jsx"。之后,babel就会自动寻找script中含有该标记的脚本,并将其转译为一般js代码。
    不过动态链接一般是比较慢的。你可以自己试一试。
  5. react核心概念:react核心概念包括三个:组件(components),属性(props)和状态(states)。
    1. 组件:在react中,组件就是函数,其返回值是html标签。调用方式和普通html标签相同,用</>或者<></>来调用这样做有一个要求,就是为了让转译器区分react写的标签和html标准标签,react写的标签应当以 大写字母开头,比如ImageHomepage
      组件支持嵌套,这和我们的预期是相同的。
    2. 属性:属性(props)实际上就是组件的参数,所有传入参数作为一个json对象整体。比如下述代码:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function Header(props) {
      return <h1>{props.title}</h1>;
      }

      function HomePage() {
      return (
      <div>
      <Header title="React" />
      </div>
      );
      }
      传入的props支持对象解析,也即可以使用function Header({title})的方式来获取props中的title成员。
      下面是另一个例子:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function HomePage() {
      const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];

      return (
      <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
      {names.map((name) => (
      <li key={name}>{name}</li>
      ))}
      </ul>
      </div>
      );
      }
      此外,react需要一个id来唯一标记标签,这也是key的作用。
    3. 状态:状态是为事件处理而生的。没有状态,系统对外界的响应只能是单一策略的。如下所示
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function HomePage() {
      // ...
      function handleClick() {
      console.log('increment like count');
      }

      return (
      <div>
      {/* ... */}
      <button onClick={handleClick}>Like</button>
      </div>
      );
      }
      如果我们希望计算like的次数(实际上就是计算点击次数),我们就需要一个状态来记录。之所以不能用局部变量来保存,是因为事件响应会触发渲染,局部变量会在每一次渲染时初始化,导致结果被清除。
      在原生 JavaScript 中,如果我们希望实现记录 like 数量的功能,通常需要依赖一个全局变量 count 来保存点赞次数,并将其与响应函数关联。
      1
      2
      3
      4
      5
      6
      let count = 0; // 全局变量

      button.addEventListener('click', () => {
      count++;
      button.textContent = `Likes: ${count}`;
      });
      虽然这种方法可以工作,但在设计上并不美观。按钮的逻辑和状态依赖于全局变量,这违背了封装性和模块化的设计原则。组件(如按钮)应该具有独立管理状态和响应事件的能力,而不是依赖于更高层级或外部的结构。为了解决这些问题,react引入了state概念。允许组件拥有自己的状态,并在状态改变的时候自动触发界面更新,而不需要手动操作dom
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function LikeButton() {
      const [count, setCount] = React.useState(0);

      return (
      <button onClick={() => setCount(count + 1)}>
      Likes: {count}
      </button>
      );
      }
      通过这种方式,按钮的逻辑和状态被封装到了组件的内部,并且界面渲染和组建的状态绑定在了一起。既美观又自然。
      与state密切相关的一个技术是hook。英文里的hook可以指墙上用来挂东西的钩子。在编程中,hook指的是系统为本地化编程/功能扩展 预留的接口。
      比如说上面说到的usestate,就是系统的一个hook。它允许你访问react的状态管理系统,对当前元素的状态进行有限的管理。
  6. 服务器端和客户端环境:web应用一般分为客户端和服务器端两端。客户端处理用户的种种原始需求,转化成为对对应资源的请求。服务器端处理请求,返回种种服务(比如最常见的http)。
    react支持对上述内容的模拟,将应用中的所有组件划分为 服务器端组件(server components)客户端组件(client components)。服务器端组件负责静态工作,客户端则需要对客户交互做出响应。
  7. 从react到next.js:前面说了next是react的一个框架,在使用react的时候,我们就不要动态链接了,而是将react脚本下载到本地。这种方式非常快捷。
    我们如何下载呢?答案是使用node.js。只需要用npm就可以实现各种流行js库的下载与使用。下载后,本地的package.json文件内会保留有项目依赖库的信息。
    next.js为我们使用react提供了很多方面的便利,比如样式设计、路由、数据存储等等。我们接下来还会见到它们。

nextjs基础#

起步#

按照教程中的叙述下载好项目,我们接下来的笔记都基于这个项目。

一般的nextjs/react项目结构如下:

  • /app:内含所有组件、路由、逻辑实现,相当于src文件夹的地位;
    • /lib:内含通用工具函数、数据获取函数等可复用的函数;
    • /ui:内含客户端组件,比如各种卡片、表单之类的;
  • /public:内含图片等其他资源。
  • config文件:项目信息描述文件。

现代项目中更多用ts,因为ts有类型检查便于纠错。

next使用 文件系统路由 来支持网页应用的路由。所谓路由(route),指的是资源/服务的位置。网页应用需要处理用户的请求,根据路由进行服务。

在我们的项目中,page.tsx文件会被next.js自动作为网页资源的根目录(假如整个项目放在https://project/地址,那么page.tsx文件就是https://project/)。如果我们希望在新的地址上放上资源,那么只需要在page.tsx的同级文件中创建新的文件夹,next.js就会将其转化为新的地址(比如https://project/folder/)。

在每一层目录中,都至少有一个page.tsx文件和layout.tsx文件,前者是描述页面内容的,后者则描述了该目录下所有页面的外观。也就是说,layout是逐层递进的。

Regular-Expressions

这个文章我们来学一学在vscode中如何使用正则表达式。

正则表达式,顾名思义,是一个表达式。它表达的是 字符串匹配规则,也就是规定 什么样的字符串可以被匹配到 。比如\d表示匹配 单个数字 的规则,\w表示匹配单个字母、数字或者下划线等等。

如何启用正则表达式#

在vscode中,快捷键ctrl+F可以快速打开使用正则表达式匹配字符串的窗口,此时会自动打开输入框旁第三个选项 使用正则表达式(Alt+R),当然你也可以使用快捷键ctrl+H的方式打开查询窗口,再用快捷键Alt+R来决定是否启用正则表达式。

启用之后,就可以在输入框中输入正则表达式来匹配字符串了。

基本的正则表达式规则:#

这一节我们先来讲讲基础的正则表达式编写方式,旨在初步介绍如何使用正则表达式来匹配字符串。

正则表达式主要由以下几部分组成:

  1. 基本字符 :字母(abc),数字和一些英文符号(@,_)等等。例如正则表达式abc将会直接匹配字符串abc,包括abcdhfue中的abc(如果你没有启用了全字匹配Alt+W)。
  2. 元字符:元字符是正则表达式中用来表达抽象匹配规则的字符。可以分为字符类、量词类、边界、分组等若干内容。
    1. 字符类:表示一类或若干类字符,常见的字符类
      • .:匹配任意单个字符(换行符除外)。
      • \d:匹配任意数字(0-9)(digital)。
      • \D:匹配任意非数字。
      • \w:匹配任意单词字符(字母、数字或下划线)(word)。
      • \W:匹配任意非单词字符。
      • \s:匹配空白字符(空格、制表符等)(space)。
      • \S:匹配非空白字符。
      • [abc]:匹配 abc 中的任意一个。
      • [^abc]:匹配除 abc 以外的任意字符。
      • [a-z]:匹配从 az 范围内的字符。
    2. 量词:指定字符或模式的重复次数。如:
    • *:匹配 0 次或多次。如a*表示匹配任意长度的
    • +:匹配 1 次或多次。
    • ?:匹配 0 次或 1 次。
    • {n}:匹配恰好 n 次。
    • {n,}:匹配至少 n 次。
    • {n,m}:匹配 n 到 m 次。

(3)边界#

用于匹配字符串的开头或结尾:

  • ^:匹配字符串开头。
  • $:匹配字符串结尾。
  • \b:匹配单词边界。
  • \B:匹配非单词边界。

(4)分组与捕获#

将部分正则表达式组合在一起,并捕获匹配结果:

  • ():捕获分组。
  • (?:...):非捕获分组。
  • |:表示逻辑“或”,如 a|b 匹配 ab

(5)转义字符#

用于匹配元字符本身或特殊字符:

  • \:转义符,如 \. 匹配字符 .

从Md走向typst

前言:#

虽然用md写了不少报告,而且自己其实也搞了一些和md相关的工作流,但其实,如果想要扩展md的功能的话,就要改md的渲染逻辑或者使用html标签。

不过确实有比md更好用的文档标记语言,这就是typst。typst拥有非常强大的 排版 功能,比如字体格式修改、段落缩进等等,

环境:#

在vscode中安装两个插件:tinymist typstTypst companion。前者会支持实时预览,后者会支持一些简单的语法高亮。

你可能还需要下载typst编译器来导出pdf。

开始:#

我们首先创建一个.typ文件,这是typst编辑器可以识别并编译生成pdf的文件的后缀。

一般文章分为标题、段落、数学公式、图表等。在typst中,文段就像md中那样简单。typst会自动将 分段 的纯文本识别为 自然段落。参见template.typ的解释。

typst的基础语法非常简单,比如用=表示一级标题,==就是二级标题(以此类推),

Ps1历险记

说点闲话#

所谓ps1,全称为Windows Powershell。顾名思义,这是一种用在win平台上的shell。而shell是用户与操作系统内核进行交互的接口。一般而言,shell可以执行各种命令,运行脚本,控制系统环境等等。

几乎所有常见的平台都会给出shell接口,比如赫赫有名的shbashzsh。而powershell则是用于win平台上的shell,也就是说,powershell为win用户提供了和操作系统交互的深层接口。

图形化界面难以自动化,这是为了便用性而做的牺牲。但是如果为了追求效率,那么就有必要用脚本来代替重复的工作。可以编写.ps1脚本来取代一些重复性的命令输入,并在终端中采用.\scriptName.ps1的形式来调用。

需要注意的是,windows本身限制.ps1脚本的运行,你需要修改当前终端的脚本运行权限才能运行写好的脚本,这可以算是一种保护。

以上为一些闲话。

我的ps1编写环境#

我使用vscode作为ps1的编辑器。vscode上微软也写好了供ps1编写的插件,可以让你方便地写相关的脚本。直接在插件中搜索PowerShell就行。