全栈开发之路

Backed Develop

环境配置

  • 创建文件夹

  • 创建api文件夹

    • 命令npm init初始化

    • 安装Express,使用npm install express

    • 安装依赖,npm install -D typescript tsx @types/express

    • 创建tsconfig.json文件

      • (代码如下)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        {
        "compilerOptions": {
        "module": "ESNext", // Use ESNext for ESM
        "target": "ES2020", // Target modern ECMAScript versions
        "moduleResolution": "node",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "outDir": "./dist", // Output directory for compiled files
        "strict": true, // Enable strict type-checking options
        "skipLibCheck": true, // Skip type checking of declaration files
        "resolveJsonModule": true, // Include JSON imports
        "forceConsistentCasingInFileNames": true,
        "noEmit": false, // Allow emitting output
        "isolatedModules": true, // Required for using ESM modules
        "baseUrl": ".", // Allow absolute imports relative to project root
        "paths": {
        "*": ["node_modules/*"]
        }
        }
        }
  • 编写hello world示例程序

    • 创建src目录,创建index.ts文件

    • (代码如下)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import express from 'express';

      const port = 3000;
      const app = express();

      app.get('/', (req, res) => {
      res.send('Hello World!')
      })

      app.listen(port, () => {
      console.log(`Example app listening on port ${port}`)
      })
    • 这时候尝试运行node src/index.ts会发生importtype错误

      1
      2
      3
      4
      "name": "ecommerce-api",
      "version": "1.0.0",
      "main": "index.js",
      "type": "module", //添加这一行,可以解决import问题

      然后运行命令node --import=tsx src/index.ts解决type问题

    • 修改package.json文件,之后运行使用npm run dev即可

      1
      2
      3
      4
      5
      6
      "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "dev": "node --import=tsx --watch --env-file=.env src/index.ts",
      "build": "tsc"
      },
      //添加dev命令,同时添加--watch检查变化,创建.env文件,添加环境变量
  • 配置git

    • 返回上一级文件,运行git init

    • 创建.gitignore文件,配置一些基本信息

      1
      2
      3
      node_modules
      dist
      .env
    • 之后就和正常的上传github的过程是一样的(我在从0到1里也有写)

  • 安装vscode插件

    • thunder client,用于测试api,也可以使用postman,看个人吧

路由和控制概念

  • 通过发送请求,返回信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    app.get('/products', (req, res) => {
    res.send('the list of products');
    })

    app.get('/products/:id', (req, res) => {
    console.log(req.params); //id
    res.send('A product');
    })

    app.post('/products', (req, res) => {
    res.send('New product created');
    })

    使用路由之后

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import express, { Router } from 'express';

    const router = Router();

    router.get('/', (req, res) => {
    res.send('the list of products');
    })

    app.use('/products', router);
  • 将路由放置到另外的地方

    • 在src目录下创建文件夹routes/products/index.ts

    • 将上面部分代码移动进入其中

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      import { Router } from 'express';

      const router = Router();

      router.get('/', (req, res) => {
      res.send('the list of products');
      })

      router.get('/:id', (req, res) => {
      console.log(req.params);
      res.send('A product');
      })

      router.post('/', (req, res) => {
      res.send('New product created');
      })

      export default router;
    • 修改原来的index.ts

      1
      2
      3
      import productsRoutes from './routes/products/index';

      app.use('/products', productsRoutes);
  • 添加控制组件

    • 创建文件routes/products/productsController.ts

      1
      2
      3
      4
      5
      6
      import { Request, Response } from "express";

      export function listProducts (req: Request, res: Response) {
      res.send('the list of products qaq');
      }
      //剩下部分类似...
    • 修改routes/products/index.ts文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      import { 
      listProducts,
      getProductById,
      createProduct,
      updateProduct,
      deleteProduct,
      } from './productsController';

      const router = Router();

      router.get('/', listProducts);
      router.get('/:id', getProductById);
      router.post('/', createProduct);
      router.put('/:id', updateProduct);
      router.delete('/:id', deleteProduct);
  • 尝试传送数据

    • 在index.ts文件中添加

      1
      2
      3
      import express, {json} from 'express';

      app.use(json());//将req解析为json
    • 可以在productsController.ts文件中添加代码查看

      1
      2
      3
      4
      export function createProduct (req: Request, res: Response) {
      console.log(req.body); //添加这个
      res.send('createProduct');
      }
  • 添加中间件?

    1
    2
    3
    4
    5
    import express, { json, urlencoded } from 'express';

    app.use(urlencoded({ extended: false }));
    //urlencoded 中间件负责将 application/x-www-form-urlencoded 编码的数据解析为 JavaScript 对象。
    //extended: false 限制了解析的数据结构(只能是字符串或简单的数组对象),而 extended: true 允许解析更复杂的嵌套数据结构。

建立数据库以及ORM工具

  • 这里使用Genezio申请了一个数据库使用

  • 这里打算使用drizzle作为ORM交互工具

    • 然后…然后请去对应的官网完成安装(^▽^)

    • 创建文件src/db/index.ts (example)

      1
      2
      3
      4
      5
      6
      7
      8
      import { drizzle } from "drizzle-orm/node-postgres";
      import { Pool } from "pg";

      const pool = new Pool({
      connectionString: process.env.DATABASE_URL!,
      });

      export const db = drizzle({ client: pool });
    • 然后创建schema,这里我创建productsSchema.ts (example)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      import { 
      integer,
      pgTable,
      varchar,
      text,
      doublePrecision
      } from "drizzle-orm/pg-core";

      export const productsTable = pgTable("products", {
      id: integer().primaryKey().generatedAlwaysAsIdentity(),
      name: varchar({ length: 255 }).notNull(),
      description: text(),
      image: varchar({ length: 255 }),
      price: doublePrecision().notNull(),
      });
    • (对了,这里移除了import 'dotenv/config是因为在package.json里面已经添加了–env-file=.env)

    • 然后按照文档创建drizzle.config.ts文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import { defineConfig } from 'drizzle-kit';

      export default defineConfig({
      out: './drizzle',
      schema: ['./src/db/productsSchema.ts'],//这里是string []
      dialect: 'postgresql',
      dbCredentials: {
      url: process.env.DATABASE_URL!,
      },
      verbose: true,
      strict: true,//more log
      });
    • 然后按照官方文档里面的generate,migrate进行使用(。。喵的我连接不上,气死我了沟槽的闭关锁国)——现在在使用Neon,Thanks♪(・ω・)ノ

    • 然后按照文档,编写需要的API

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      import { Request, Response } from "express";
      import { db } from "../../db/index";
      import { productsTable } from "../../db/productsSchema";
      import { eq } from "drizzle-orm";

      export async function listProducts (req: Request, res: Response) {
      try {
      const products = await db
      .select()
      .from(productsTable);

      res.json(products);
      } catch(err) {
      res.status(500).send(err);
      }
      }//示例

信息验证检查

有像zod这样的工具:npm intall zod

这里选择创建文件**/src/middlewares/validationMiddleware.ts**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Request, Response, NextFunction } from 'express';
import { z, ZodError } from 'zod';

export function validateData(schema: z.ZodObject<any, any>) {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse(req.body);
next();
} catch (error) {
if (error instanceof ZodError) {
const errorMessages = error.errors.map((issue: any) => ({
message: `${issue.path.join('.')} is ${issue.message}`,
}));
res.status(400).json({ error: 'Invalid data', details: errorMessages });
} else {
res.status(500).json({ error: 'Internal Server Error' });
}
}
};
}

然后在routes的index.ts进行简单的测试

1
2
3
4
5
6
7
8
9
10
11
import { validateData } from '../../middlewares/validationMiddleware';

import { z } from 'zod';

const createProductSchema = z.object({
name: z.string(),
price: z.number(),
})

router.post('/', validateData(createProductSchema), createProduct);
//这里省略了部分代码的

然后…接下来这部分就有点confused了,主要应该是让req里面不管传递什么,都只保留需要的部分数据部分。

首先创建src/types/express/index.d.ts文件处理类型检查

1
2
3
4
5
6
7
8
9
10
export {};

declare global {
namespace Express {
export interface Request {
userId?: Number;
cleanBody?: any;
}
}
}

然后在上面的validationMiddleware.ts做修改(这里得先安装lodash)

1
2
3
4
5
6
import _ from 'lodash';
try {
schema.parse(req.body);
req.cleanBody = _.pick(req.body, Object.keys(schema.shape));
next();
}

然后在productSchema.ts文件中添加

1
2
3
4
5
6
7
8
9
export const createProductSchema = createInsertSchema(productsTable).omit({
id: true,
});

export const updateProductSchema = createInsertSchema(productsTable)
.omit({
id: true,
})
.partial();

然后同上所述,在index.ts中引入中间件

1
2
router.post('/', validateData(createProductSchema), createProduct);
router.put('/:id', validateData(updateProductSchema), updateProduct);

最后记得将productController.ts文件中的req修改为req.cleanBody


Auth验证和Token检查使用

首先,如上所述,需要创建一个user表(usersSchema.ts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const usersTable = pgTable("users", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),

email: varchar({length: 255}).notNull().unique(),
password: varchar({length: 255}).notNull(),
role: varchar({length: 255}).notNull().default('user'),

name: varchar({length: 255}),
address: text(),
});

export const createUserSchema = createInsertSchema(usersTable).omit({
id: true,
role: true,
});

export const loginSchema = createInsertSchema(usersTable).pick({
email: true,
password: true,
});
// 这里也注意一下,之后测试的时候忘了会强制把role默认为"user",所以要验证路由的话需要再后台修改之后再登录一次,再获取新的token,然后还有就是对应的type不能写错,还有就是邮箱必须是唯一的,每次注册不要忘了!!

然后修改drizzle.config.ts文件,使用对应的schema

1
schema: ['./src/db/productsSchema.ts', './src/db/usersSchema.ts'],

然后在routes文件夹中创建auth/index.ts,同上面product作用类似

这里使用了两个librarybcryptjs && jsonwebtoken(请安装)

ok,举一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const router = Router();

const generateUserToken = (user: any) => {
return jwt.sign({ userId: user.id, role: user.role }, 'your-secret', {
expiresIn: '30d',
});
};

router.post('/register', validateData(createUserSchema), async (req, res) => {
try {
const data = req.cleanBody;
data.password = await bcrypt.hash(data.password, 10);

const [user] = await db
.insert(usersTable)
.values(data)
.returning();

// @ts-ignore
delete user.password;
const token = generateUserToken(user);

res.status(201).json({ user, token });
} catch(err) {
res.status(500).send('Something went wrong');
}
})

export default router;

然后在middlewares文件夹中新建一个中间件authMiddleware.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export function verifyToken(req: Request, res: Response, next: NextFunction) {
const token = req.header('Authorization')?.replace('Bearer ', '');

if(!token) {
console.log("not token");
res.status(401).json({ error: 'Access denied' });
return;
}

try {
//decode jwt token data
const decoded = jwt.verify(token, 'your-secret');
if(typeof decoded !== 'object' || !decoded?.userId) {
res.status(401).json({ error: 'Access denied' });
return;
}
req.userId = decoded.userId;
req.role = decoded.role;
next();
} catch(err) {
console.error("JWT verification error:", err);
res.status(401).json({ error: 'Access denied' });
}
}

export function verifySeller(req: Request, res: Response, next: NextFunction) {
const role = req.role;

if(role !== 'seller') {
console.log("not a seller");
res.status(401).json({ error: 'Access denied' });
return;
}

next();
}

最后再回到products的index.ts,添加验证:(example)

1
router.post('/', verifyToken, verifySeller, validateData(createProductSchema), createProduct);

以上过程可以自己发送请求测试

————来自之后癫狂的我:喵的,前面没发现到这里就不行了,没验证,导致后面全部都出问题,记住token要放在Auth那里,而不是放在header里,前面因为懒得检查导致了这么多的错误!!以后真别懒了!!fuck

{

  1. JWT(JSON Web Token)概述

JWT 是一种用于安全传输信息的紧凑且自包含的格式。它通常用于在客户端和服务器之间传递认证信息,特别是用于 身份验证(Authentication)和 授权(Authorization)场景。

JWT 通常由三部分组成:

  • Header:头部,指定签名算法(如 HMAC SHA256 或 RSA)。
  • Payload:有效载荷,存储传递的数据(如用户 ID、角色等)。
  • Signature:签名,用于验证 token 是否被篡改。
  1. decoded 是什么?

在你的代码中,jwt.verify(token, 'your-secret') 这行代码的作用是 验证和解码 传入的 JWT Token。具体来说,decoded 是经过 jwt.verify 解码后的结果,包含了 JWT 中的有效载荷部分。

  • 如果 token 合法且没有被篡改,jwt.verify 会返回一个包含 payload 数据的对象,通常是用户身份的相关信息,比如 userIdrole 等。
  • 如果 token 无效或被篡改,jwt.verify 会抛出错误。

假设你的 JWT 的 payload 部分存储了以下信息:

1
2
3
4
json{
"userId": "123",
"role": "seller"
}

当你调用 jwt.verify(token, 'your-secret') 后,decoded 就会是:

1
2
3
4
json{
"userId": "123",
"role": "seller"
}
  1. req.userId = decoded.userIdreq.role = decoded.role 的作用

这两行代码的目的是将 JWT 中的有效载荷信息(即 userIdrole附加到请求对象 req,这样在后续的中间件或路由处理函数中,你可以方便地访问这些信息。

  • **req.userId = decoded.userId;**:将 decoded 中的 userId 存储到 req.userId,意味着你可以在其他地方通过 req.userId 获取当前请求用户的 ID。
  • **req.role = decoded.role;**:将 decoded 中的 role 存储到 req.role,意味着你可以在其他地方通过 req.role 获取当前请求用户的角色(如管理员、商家等)。

}


构建dist && deploy

众所周知,只需要输入npm run build就可以了

然后在命令行输入node dist/src/index.js尝试运行之后,会发现——诶,不行

没错,我们要把全部文件中的那些引用文件添加”.js”后缀

然后再构建之后再使用命令,会发现——诶,还是不行

其实是因为我们没使用环境变量(^▽^)

所以要输入node --env-file=.env dist/src/index.js

之后使用的Genezio进行项目的部署(请查看文档docs)

这里我看视频里没有写,但是我的错误日记里写了找不到dist里面的export handler,因为我添加完代码之后没有再重新build。。很合理,但是不知道为什么那个youtube视频里面没有build也行??就很奇怪,总而言之,有错误日记真是好事情啊~

之后如果在本地运行会出现Completed running 'src/index.ts'这种情况QAQ(之前没搞清楚的时候着实被折磨了一下)

之后如果要运行的话需要修改package.json里的scripts为:

1
"dev": "NODE_ENV=dev node --import=tsx --watch --env-file=.env src/index.ts",

因为部署的时候修改了index.ts为:

1
2
3
4
5
if (process.env.NODE_ENV === "dev") {
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
})
}

然后…我们就会发现,还是不行?

于是,万能的AI,快救救我吧——于是,AI降临了,并传下神谕:

{

这个错误通常是因为在 Windows 系统中使用 NODE_ENV=dev 的方式设置环境变量时,命令行无法识别这种语法。在 Windows 环境中,设置环境变量的语法与类 Unix 系统(如 Linux、macOS)不同,因此会导致 'NODE_ENV' 不是内部或外部命令 错误。

解决办法:

1. 使用 cross-env 模块

cross-env 是一个用于跨平台设置环境变量的工具,它可以使得在 Windows 和 Linux/macOS 上的命令都能正常工作。

  • 步骤 1:安装 cross-env

    你可以通过 npm 安装 cross-env

    1
    bashnpm install cross-env --save-dev
  • 步骤 2:修改 package.json 中的 scripts

    package.json 文件中的 dev 脚本部分,使用 cross-env 来设置环境变量:

    1
    2
    3
    json"scripts": {
    "dev": "cross-env NODE_ENV=dev node --import=tsx --watch --env-file=.env src/index.ts"
    }

    这样,cross-env 会确保在所有操作系统上正确地设置 NODE_ENV 环境变量。

}

后端工程暂时到这里告一段落吧QAQ

(PS:感觉数据库的设计还是有点麻烦的)


MOBILE-APP

环境配置

  • 到根目录下运行npx create-expo-app@latest --template 命令
  • 然后这里选择的是blank typescript
  • 然后请按照expo官方文档中的expo router install的引导页面进行环境安装配置
  • (QAQ,然后不知道为啥没有代码补全,所以快速安装了,然后遇到一个离谱问题:File 'expo-module-scripts/tsconfig.base' not found.ts,然后找了半天。。发现重启编译器就行, 好气)
  • 学到一个新东西,expo go还可以全世界连接hh

安装使用gluestack-ui(!!!)

按照官方文档肯定可以简简单单完——完蛋,不出所料,应该——是要出问题了。

喵的,不知道为什么添加组件的时候访问超时无法连接。但是我觉得这个UI库应该还挺重要的,所以还得想想其它的办法才行。

OK,所谓的科学就是不断试验学习到红温然后解决问题,根据如下这篇文章的内容解决了这个问题

[网址]:完美解决 git 报错 “fatal: unable to access ‘https://github.com/.../.git‘: Recv failure Connection was rese-CSDN博客

太好了!O(∩_∩)O

React-native简单介绍?

(不知道为什么他用了好多组件啊,我个人觉得这些简单的功能有tailwindcss应该都挺简单就可以写出来了吧)

目前RootLayout如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import "@/global.css";
import { Stack } from "expo-router";
import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider";

export default function RootLayout() {
return (
<GluestackUIProvider mode="light">
<Stack>
<Stack.Screen name="index" options={{ title: "Shop" }} />
<Stack.Screen name="product/[id]" options={{ title: "Product" }} />
</Stack>
</GluestackUIProvider>
);
}

应该很显然可以知道,stack用于显示上面那个状态栏的名字,当然也可以直接在页面中使用,如这里在/product/[id]中使用 <Stack.Screen options={{ title: product.name }} />

fetch数据简单介绍?

因为已经有backed,所以相对来说已经挺简单的了。创建一个api文件夹,存放需要的api

1
2
3
4
5
6
7
8
const API_URL = process.env.EXPO_PUBLIC_API_URL;

export async function listProducts() {
const res = await fetch(`${API_URL}/products`);
const data = await res.json();

return data;
}

使用时大概如下:

1
2
3
4
5
6
7
8
9
10
const [products, setProducts] = useState();

useEffect(() => {
const fetchProducts = async () => {
const data = await listProducts();
setProducts(data);
};

fetchProducts();
}, []);

补充:使用了TanStackQuery

TanStack Query(以前叫做 React Query)是一个强大的数据获取、缓存和同步管理库,特别适用于 React 应用中。它帮助开发者轻松地管理应用中的数据流,包括服务器端的数据获取、缓存管理、背景数据同步和请求的重试等功能。

主要功能

  1. 数据获取与缓存
    • TanStack Query 使得获取数据变得简单,通过定义 queriesmutations,你可以轻松地发起 HTTP 请求并缓存数据,避免不必要的重复请求,提高性能。
    • 它会自动缓存成功获取的数据,缓存中的数据会在组件重新渲染时复用,从而减少网络请求和提升应用性能。
  2. 自动后台数据同步
    • TanStack Query 会自动在后台重新获取数据,确保你的应用始终显示最新的数据。它会基于某些条件(如窗口重新聚焦、定时重新请求等)重新加载数据。
  3. 自动重试与失败处理
    • 当请求失败时,TanStack Query 可以自动重试请求,支持定制化的重试次数和重试间隔。
  4. 状态管理
    • 它简化了从 API 获取数据时的状态管理,比如加载中、成功、失败等状态,开发者可以直接通过 TanStack Query 提供的钩子来管理这些状态,而不需要手动管理复杂的加载和错误状态。
  5. 分页与无限加载
    • TanStack Query 支持分页和无限加载模式,适用于需要按页加载数据的场景,如数据表格或无限滚动列表。
  6. Mutation 支持
    • 除了查询数据,TanStack Query 还支持对数据的变更(如创建、更新和删除操作)。这些操作被称为“mutations”,它提供了非常便捷的钩子来处理这些操作的成功、失败以及相关的状态管理。

( 血的教训孩子们(o(╥﹏╥)o))

我在后端代码里写了,注册邮箱必须唯一,然后我在前端测试的时候忘记了,结果各种找错问题,折腾了半天,突然灵感一闪——艹,我是不是用了以及用过的邮箱。这真的是经典问题了,之前我开发后端的时候就遇到过这个问题了,哎,以后还是在后端那边打印点提醒吧。吐了,呕

补充:Zustand

Zustand 是一个轻量级、简单且强大的状态管理库,特别适合 React 应用程序。它提供了一种简洁的 API 用于管理应用中的全局状态,并且不需要太多的样板代码。Zustand 的设计目标是简单、直观,并且尽可能少的抽象。

核心特点:

  1. 简洁易用
    • 你只需要定义一个状态(store)和一些操作(actions),就可以开始管理状态。
    • 没有复杂的概念,如 reducers、actions、dispatch 等,使用起来非常直观。
  2. 无依赖性
    • Zustand 没有外部依赖,除了 React。它是纯粹的 JavaScript,因此不需要额外的库或工具来运行。
  3. 自动订阅
    • Zustand 内部自动管理组件的订阅,组件会在状态变化时自动重新渲染,确保状态与 UI 的同步。
  4. 支持持久化
    • Zustand 可以与本地存储结合使用,支持持久化状态,可以将状态存储到 localStoragesessionStorage
  5. 极简的 API
    • Zustand 提供了非常简单的 API,只需要一个函数 create 来创建一个 store。

基本用法

  1. 安装: 如果你使用 npm 或 yarn,你可以通过以下命令安装 Zustand:

    1
    bashnpm install zustand

    或者:

    1
    bashyarn add zustand
  2. 创建 Store: 使用 create 函数来创建一个状态管理 store。你可以在 store 中定义状态和方法(actions)来改变状态。

    1
    2
    3
    4
    5
    6
    7
    8
    javascriptimport create from 'zustand';

    // 创建一个 store
    const useStore = create((set) => ({
    count: 0,
    increase: () => set((state) => ({ count: state.count + 1 })),
    decrease: () => set((state) => ({ count: state.count - 1 })),
    }));
    • useStore 是一个 hook,可以在 React 组件中使用。
    • set 用来更新状态,state 表示当前的状态。
    • set 里面定义的方法会被用作操作状态(如 increasedecrease)。
  3. 使用 Store: 在 React 组件中,你可以通过 useStore hook 获取状态和操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    javascriptimport React from 'react';
    import { useStore } from './store';

    const Counter = () => {
    const { count, increase, decrease } = useStore();
    return (
    <div>
    <p>Count: {count}</p>
    <button onClick={increase}>Increase</button>
    <button onClick={decrease}>Decrease</button>
    </div>
    );
    };

    export default Counter;
    • useStore 返回的对象包含了状态(如 count)和操作(如 increasedecrease)。
    • 当调用 increasedecrease 时,状态会被更新,组件会自动重新渲染。
  4. 自定义状态和方法: 你可以根据需要自定义状态和方法。Zustand 允许你非常灵活地组织 store。

    1
    2
    3
    4
    5
    javascriptconst useStore = create((set) => ({
    user: null,
    setUser: (user) => set({ user }),
    clearUser: () => set({ user: null }),
    }));
    • 在这个例子中,user 是一个状态,setUserclearUser 是方法,用来设置和清除用户数据。

其他特点:

  • 派发异步操作: Zustand 也支持异步操作。例如,你可以在方法中执行异步请求:

    1
    2
    3
    4
    5
    6
    7
    8
    javascriptconst useStore = create((set) => ({
    data: null,
    fetchData: async () => {
    const response = await fetch('/api/data');
    const data = await response.json();
    set({ data });
    },
    }));
  • 持久化状态: Zustand 提供了简单的持久化机制。你可以通过 persist 中间件将状态存储到 localStoragesessionStorage 中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    javascriptimport create from 'zustand';
    import { persist } from 'zustand/middleware';

    const useStore = create(
    persist(
    (set) => ({
    count: 0,
    increase: () => set((state) => ({ count: state.count + 1 })),
    }),
    {
    name: 'counter-storage', // 持久化到 localStorage 中的名字
    }
    )
    );