[copy airbnb] 7. 收藏功能

1、本章介绍

本章主要实现收藏功能,涉及到hooks的使用、api路径的作用。

2、hooks

自从v16.8版本以来,react引入了hooks的概念。

16.8以前组件的标准写法是class(类),下面是一个简单的组件类:

import React, { Component } from "react";

export default class Button extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click me, please" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}

这个组件类仅仅是一个按钮,但可以看到,它的代码已经很”重”了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂。

React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。

React 早就支持函数组件,下面就是一个例子。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

但是,这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。

React Hooks 的设计目的,就是加强版函数组件,完全不使用”类”,就能写出一个全功能的组件。

2.1 、什么是hooks(钩子)

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码”钩”进来。 React Hooks 就是那些钩子。也就是说组件是纯函数这个形式是不能变的,其他需要的功能使用hooks组合进来即可。比如当用户希望使用生命周期的时候:

hooks中生命周期的实现:

didMount

利用 Effect Hook 依赖不变不会再次执行的特性,依赖传入空数组即可实现挂载时执行一次的特性

1
2
3
const useMount = (cb) => {
useEffect(cb, [])
}

update

利用 useRef 对象在组件的整个生命周期内保持不变的特性,在组件初始化时初始化 ref 对象,判断 ref 对象的状态决定是否执行回调

1
2
3
4
5
6
7
8
9
const useUpdate = (cb, deps) => {
const ref = useRef(0)
useEffect(() => {
if (ref.current) {
cb()
}
ref.current = ref.current + 1
}, deps)
}

Unmout

利用 Effect Hook 返回值在组件卸载时执行的特性,在组件初始化时把回调作为其返回值,即可实现卸载执行的特性

1
2
3
4
5
const useUnmout = (cb) => {
useEffect(() => {
return cb
}, [])
}

2.2、本项目中的hooks

本项目中的hooks其实是自定义hooks,主要思想是能在不同组件内进行复用的功能的抽象,以实现收藏功能的hooks类为例:

import {NextResponse} from "next/server";

import getCurrentUser from "@/app/actions/getCurrentUser";
import prisma from "@/app/libs/prismadb";

interface IParams{
    listingId?:string;
}

export  async function POST(request:Request,{params}:{params:IParams}){
    const currentUser = await getCurrentUser();
    if(!currentUser){
        return NextResponse.error();
    }

    const {listingId} = params;

    if(!listingId || typeof listingId !== 'string'){
        throw new Error("Invalid ID");
    }

    let favoriteIds = [...(currentUser.favoriteIds||[])];
    favoriteIds.push(listingId);
    const user = await prisma.user.update({
        where:{
            id:currentUser.id
        },
        data:{
            favoriteIds
        }
    })

    return NextResponse.json(user);
}


export async function DELETE(request:Request,{params}:{params:IParams}){
    const currentUser = await getCurrentUser();
    if(!currentUser){
        return NextResponse.error();
    }

    const {listingId} = params;

    if(!listingId || typeof listingId !== 'string'){
        throw new Error("Invalid ID");
    }

    let favoriteIds = [...(currentUser.favoriteIds||[])];

    favoriteIds = favoriteIds.filter((id)=>id!==listingId)

    const user = await prisma.user.update({
        where:{
            id:currentUser.id
        },
        data:{
            favoriteIds
        }
    })

    return NextResponse.json(user);
}

可以看出,这里的hooks可以在不同组件内使用,其实概念上更接进入utils,所以说本项目中的“src/app/hooks”的作用其实仅仅只是将可复用逻辑抽象出来供不同组件复用的一个公共区域罢了。

3、nextjs的api路径对功能的影响

这里使用src/app/api/favorites/[listingId]/route.ts这个特殊的例子来说明

首先在nextjs14及近期版本中,src/app/api是个特殊的路径,其下放置的都是接口的实现,而api下的文件夹层级映射了api的实际路径:

http://localhost:3000/api/favorites/6657eb79027ff7dc31c034fd -> src/app/api/favorites/[listingId]/route.ts中的api的实现方案。可以看见通过路径中的[listingId]通配符,nextjs将6657eb79027ff7dc31c034fd这个值赋值给了listingId这个参数名。并可以在route.ts中取得listingId的值:

export  async function POST(request:Request,{params}:{params:IParams}){
    const currentUser = await getCurrentUser();
    if(!currentUser){
        return NextResponse.error();
    }

    const {listingId} = params;

    if(!listingId || typeof listingId !== 'string'){
        throw new Error("Invalid ID");
    }

    let favoriteIds = [...(currentUser.favoriteIds||[])];
    favoriteIds.push(listingId);
    const user = await prisma.user.update({
        where:{
            id:currentUser.id
        },
        data:{
            favoriteIds
        }
    })

    return NextResponse.json(user);
}

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部