next.js + react基础(持续更新)

umijsicon-default.png?t=N7T8https://umijs.org/docs/introduce/introduce

react:环境搭建、jsx、组件、useState、useEffect、useRef、自定义Hook、智能组件和UI组件

redux:RTK、同步状态、异步状态、状态和视图分离

Router:基础使用、嵌套路由、路由模式、声明/编程式导航

基础扩展:useMemo/useCallback、useReducer、useImperativeHandle、Class API vc Hooks、新一代状态管理zustand

react+ts:react+ts基础、axios+ts、zustand+ts

渲染环境 【重点】

有两种环境可以呈现 Web 应用程序:客户端和服务器。

客户端渲染 CSR:在客户端渲染中,服务器会发送一个最小化的HTML页面和一些JavaScript代码给客户端。然后,客户端的浏览器会执行这些JavaScript代码,动态地生成并插入页面的内容。

优势:快速页面切换,减轻服务器压力

劣势:SEO不友好,首屏加载慢,对客户端性能要求高

服务端渲染 SSR:服务器负责将页面的HTML内容渲染成最终的形式,再发送给客户端。用户在访问页面时,收到的是已经渲染好的HTML内容,而不是一段空白的HTML和一堆JavaScript代码。

优势

  • SEO: 搜索引擎对于能够提供已渲染HTML内容的页面更加友好,可以更容易地索引和理解页面的内容
  • 首屏加载性能:用户在访问页面时能够更快地看到内容,因为不需要等待大量的JavaScript代码下载和执行
  • 无需客户端渲染能力

劣势:页面切换慢,服务器压力大

Next.js 

next.jsicon-default.png?t=N7T8https://nextjs.org/docs/getting-started/installation

 建议Node.js环境在18.17以上 

手动创建新的 Next.js 【不推荐】

npm install next@latest react@latest react-dom@latest

# 具体配置流程请查看官网

注意:用tsx文件会报错,运行npm run dev 启动开发服务器,会自动引入ts开发依赖,创建配置文件

1、创建一个 app/ 文件夹,然后添加一个 layout.tsx 和 page.tsx 文件。当用户访问应用程序的根目录时,将呈现这些内容 ( / )。推荐


// 在内部 app/layout.tsx 创建一个根布局,其中包含所需的 <html> 和 <body> 标签
export default function RootLayout({
    children,
  }: {
    children: React.ReactNode
  }) {
    return (
      <html lang="en">
        <body>{children}</body>
      </html>
    )
  }


// 创建一个 app/page.tsx 包含一些初始内容的主页
export default function Page() {
  return <h1>Hello, Next.js!</h1>
}

2、如果您更喜欢使用页面路由器而不是应用程序路由器,则可以在项目的根目录下创建一个 pages/ 目录。【记得删除 app/page.tsx,有冲突】 可选,不推荐

  •  在 pages 文件夹中添加一个 index.tsx 文件。这将是您的主页 ( / )
  •  在pages/ 添加一个 _app.tsx 文件来定义全局布局。详细了解自定义应用文件。

  •  在pages/ 添加一个 _document.tsx 文件来控制来自服务器的初始响应。详细了解自定义文档文件

// _app.tsx
import type { AppProps } from 'next/app'
 
export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

// _document.tsx
import { Html, Head, Main, NextScript } from 'next/document'
 
export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

// index.tsx 
export default function Page() {
    return <h1>Hello, Next.js!</h1>
  }

3、创建一个 public 文件夹来存储静态资产,例如图像、字体等。然后,代码可以从基本 URL ( ) 开始引用 public 目录中的文件 / 。 【可选】

4、运行npm run dev启动项目。【初始化已完成】

根布局必须定义 <html> 和 <body> 标记,因为 Next.js 不会自动创建它们。

默认情况下,根布局是服务器组件,不能设置为客户端组件。

create-next-app【推荐】

建议使用 create-next-app 启动新的 Next.js 应用程序,它会自动为您设置所有内容。

npx create-next-app@latest

#npx Node.js工具命令,查找并执行后续的包命令
#create-next-app 设置 Next.js 应用程序

React 

zh-hans.reacticon-default.png?t=N7T8https://zh-hans.react.dev/ 

react.devicon-default.png?t=N7T8https://react.dev/

 

React 和 Next.js

renderingicon-default.png?t=N7T8https://nextjs.org/docs/app/building-your-application/rendering

  1. React 和 Next.js 允许您创建混合 Web 应用程序,其中部分代码可以在服务器或客户端上呈现。
  2. 在 React 中,您可以选择将客户端-服务器网络边界放置在最有意义的位置。
  3. 你可以使用 React "use client" 约定来定义边界。还有一个 "use server" 约定,它告诉 React 在服务器上做一些计算工作。 

基础语法 

创建react项目【建议直接使用nextjs创建】

npx create-react-app 你的项目名
# create-react-app (核心包),用于创建react项目

如果你的标签和 return 关键字不在同一行,则必须把它包裹在一对括号中

jsx:js和xml(html)的缩写,表示在js代码中编写html模块结构。【html声明式模板写法,js的可编程能力】 

babelicon-default.png?t=N7T8http://babeljs.io/

1、大括号语法{}

jsx可以通过大括号语法{}识别js中的表达式,比如常见的变量、函数调用、方法调用等等

  • 使用引号传递字符串
  • 使用js变量
  • 函数调用和方法调用
  • 使用js对象
  • if、switch语句、变量声明属于语句,不是表达式。不能出现{}

2、基础样式

        行内样式、class类名控制

3、map方法

jsx可以使用原生js中的map方法遍历渲染列表

4、条件渲染

可以通过逻辑与运算符&&、三元表达式(?:)实现基础的条件渲染

5、事件绑定

事件绑定(属于客户端渲染,添加use client标识),语法:on+事件名称={方法名},遵循驼峰命名法

        注意:传递自定义参数,不能直接写函数调用,改成箭头函数写法。

6、自定义组件

一个组件就是用户界面的一部分,拥有自己的逻辑和外观,组件之间可以互相嵌套,也可以多次复用【自定义组件首字母要大写,否则不起作用】

/* 页面是路由独有的 UI。您可以通过从 page.js 文件导出组件来定义页面。
使用嵌套文件夹定义路由,并使用 page.js 文件使路由可公开访问。*/
"use client"; // 绑定事件报错,添加使用客户端use client【允许在客户端(浏览器中)渲染 React 组件】
// 引入css
import './base.css'
// 引入useState
// 自定义组件首字母要大写,否则不起作用
function FirstComponent() {
  return (
    <article>
      <h1>我的第一个组件</h1>
      <ol>
        <li>组件:UI 构成要素</li>
        <li>定义组件</li>
        <li>使用组件</li>
      </ol>
    </article>
  );
}
// 变量
const count = 1;
const flag = true;

const list = [
  { id: 1, name: "小吴" },
  { id: 2, name: "小兰" },
  { id: 3, name: "小红" },
  { id: 4, name: "小明" },
];

// 函数调用测试
function getName() {
  return "函数调用";
}

// 函数调用测试
function ifCont() {
  if (count === 1) {
    return <div>数字1</div>;
  } else if (count === 2) {
    return <div>数字2</div>;
  }
}
export default function Page() {
  // 基础事件绑定
  function handleTestClick() {
    alert("点击事件");
  }
  // 自定义参数事件绑定
  function handleParamsClick(evt, params) {
    console.log(evt, params);
  }

  return (
    // 只能有一个根元素
    <section>
      <h1>注意:自定义组件首字母要大写,否则不起作用</h1>
      <FirstComponent />
      {/* 调用js对象 */}
      <div style={{ color: "blue" }} className="h-fontSize">
        {"使用引号传递字符串"} <br />
        {/* 识别js变量 */}
        {count}
        {/* 函数调用 */}
        {ifCont()}
        {getName()} <br />
        {/* 方法调用 */}
        {new Date().getDate()} <br />
      </div>

      {/* 列表渲染 */}
      {/* map循环那个结构 return结构 */}
      {/* 注意必须添加key,作用:react框架内部使用,提升更新性能 */}
      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>

      {/* 条件渲染 */}
      {/* 逻辑与 && */}
      {flag && <span>条件渲染</span>}
      <br />
      {/* 三元运算 */}
      {flag ? <span>三元运算true</span> : <span>三元运算false</span>}
      {/* 基础事件绑定 */}
      <button onClick={handleTestClick}>基础事件绑定</button>
      {/* 自定义参数事件绑定 */}
      <button onClick={(e) => handleParamsClick(e, "hui")}>自定义参数</button>
    </section>
  );
}

 7、useState(react hook)

useState是一个react hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果【本质:和普通的js变量不同的是,状态变量一旦发生变化,组件视图UI也会跟着变化(数据驱动视图)】

  • useState是一个函数,返回值是一个数组
  • 数组中的第一个参数是状态变量,第二个参数是set函数用来修改状态变量的
  • useState的参数将作为状态变量的初始值
  • 状态变量不能直接修改,直接修改无法引发视图更新。
  • 修改对象类型的状态变量,给set函数传一个全新的对象来进行修改【直接修改源对象,不引发视图变化】
  • 惯例是将这对返回值命名为 const [thing, setThing]。你也可以将其命名为任何你喜欢的名称,但遵照约定俗成能使跨项目合作更易理解。
  • 对象的展开语法是浅层的:它的复制深度只有一层。
  • 可以通过使用像 filter()map() 这样不会直接修改原始值的方法,从原始数组生成一个新的数组

8、表单绑定

【useState/useRef】

"use client"; // 使用客户端渲染
// 引入useState
import { useRef, useState } from "react";

export default function Page() {
  // num: 状态变量   setNum修改状态变量
  // useState的参数将作为num的初始值
  const [num, setNum] = useState(0);
  function handleNumClick() {
    console.log(num);
    // 作用:1、用传入的新值修改num 2、重新使用新的num渲染ui
    setNum(num + 1);
  }

  // 修改对象状态
  const [form, setForm] = useState({ name: "jocker" });
  function handleObjClick() {
    // 错误写法
    // form.name = 'clown'
    // 传入一个全新的对象
    setForm({
      ...form,
      name: "clown",
    });
  }

  // 表单绑定
  // 使用React组件的状态useState控制表单的状态
  const [inputValue,setInputValue] = useState('')

  // 通过useRef钩子函数获取dom
  // 1、创建ref对象,并与jsx绑定
  const inputRef = useRef(null)
  // 2、通过inputRef.current拿到Dom对象
  function getInputDom(){
    // console.log(inputRef.current);
    console.dir(inputRef.current);
  }

  return (
    <div>
      <button onClick={handleNumClick}>增加状态变量:{num}</button>
      <button onClick={handleObjClick}>修改对象状态:{form.name}</button>
      {/* 通过value属性绑定状态,通过change事件绑定状态同步的函数 */}
      <input ref={inputRef} value={inputValue} onChange={(e) => setInputValue(e.target.value)}></input>
      <button onClick={getInputDom}>获取dom元素:{inputValue}</button>
    </div>
  );
}

9、父子组件

父传子-props
props可传递任意的数据:数字、字符串、布尔值、数组、对象、函数、JSX
props是只读对象:子组件中只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改

特殊的prop children:在子组件标签中嵌套内容,父组件会自动在名为children的prop属性中接收改内容

子传父
在子组件中调用父组件中的函数并传递参数

使用状态提升实现兄弟组件通信
借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递

  1. A组件先通过子传父的方式把数据传给父组件
  2. 父组件拿到数据后通过父传子的方式传给B组件

使用Context机制跨层级组件通信

  1. 使用createContext方法创建一个上下文对象ctx
  2. 在顶层组件中通过ctx.Provider组件提供数据
  3. 在底层组件中通过useContext钩子函数获取数据
"use client";

import { useState,createContext, useContext } from "react";

// 父传子
// 1、父组件传递数据,子组件标签身上绑定属性
// 2、子组件接收数据,props

// 子组件
function Son(props) {
  // props: 对象里面包含了父组件传递过来的所有的数据
  // console.log(props,'参数');
  return <div>Son Component, {props.name}</div>;
}

// 特殊的prop children
function Child(props) {
  // console.log(props,'参数');
  return <div>Child Component, {props.children}</div>;
}

// 子传父
function SonParams({ onGetSonMsg }) {
  const sonMsg = "son msg";
  return (
    <div>
      Son Component
      <button onClick={() => onGetSonMsg(sonMsg)}>子传父</button>
    </div>
  );
}

// 使用状态提升实现兄弟组件通信
function AState({ onGetAName }) {
  const name = "A component"
  return (
    <div>
      A组件---------
      <button onClick={() => onGetAName(name)}>A组件:子传父</button>
    </div>
  );
}

function BState({ name }) {
  return <div>B Component---------父传子{name}</div>;
}

// 使用Context机制跨层级组件通信
const getContext = createContext("Context默认值")

function A(){
  return (
    <div>
      Context A 
      <B></B>
    </div>
  )
}

function B(){
  const msg = useContext(getContext)
  return (
    <div>
      Context B,{msg}
    </div>
  )
}
export default function Page() {
  const name = "Father Component";
  function getMsg(msg: string) {
    console.log(msg);
  }
  // 兄弟组件
  const [ABName, setABName] = useState("");

  function getAName(name) {
    setABName(name);
  }

  // Context机制
  const msgContext = "App Context msg"

  return (
    <div>
      <Son
        name={name}
        age={18}
        bol={true}
        list={["react", "test"]}
        obj={{ name: "jocker" }}
        cb={() => console.log(123)}
        child={<span>this is span</span>}
      ></Son>
      <Child>
        <span>在子组件标签中嵌套内容</span>
      </Child>
      <SonParams onGetSonMsg={getMsg}></SonParams>
      {/* 状态提升 */}
      <AState onGetAName={getAName}></AState>
      <BState name={ABName}></BState>
      {/* 使用Context机制跨层级组件通信 */}
      {/* 默认值 */}
      <A />
      {/* 自定义 */}
      <getContext.Provider value = {msgContext}>
        {/* 顶层组件 */}
        <A />
      </getContext.Provider>
    </div>
  );
}

10、useEffect(hook函数)

用于在react组件中创建不是由事件引起而是由渲染本身引起的操作。【axios,更改DOM】

由渲染本身引起的操作:不会发生任何的用户事件,组件渲染完毕之后就需要和服务器要数据

基本使用

useEffect(() => {},[])

参数1 () => {}: 副作用函数,在函数内部可以放置要执行的操作

参数2 []:在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次

useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现

依赖项副作用函数执行时机
没有依赖项组件初始渲染+组件更新时执行
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染+特定依赖项变化时执行

清除副作用

在useEffect中编写的由渲染本身引起的对接组件外部的操作【副作用操作】

比如在useEffect中开启一个定时器,想在组件卸载时把这个定时器清理掉,这个过程就是清除副作用

"use client"
import { useEffect, useState } from "react"

const url = "http://localhost:8080/users" // 查询接口

export default function Effect(){
    /*const [list, setList] = useState([])
    useEffect(() => {
        // 额外的操作 获取列表数据
        async function getList() {
            const res = await fetch(url)
            const jsonRes = await res.json()
            console.log(res);
            
            console.log(jsonRes);
            setList(jsonRes.data.channels)
           }
           getList()
    },[])*/
    const [count,setCount] = useState(0)

    // 没有依赖项  初始+组件更新
    // useEffect(() => {
    //     console.log("副作用函数执行了");
    // })

    // 空数组依赖 初始执行一次
    // useEffect(() => {
    //     console.log("副作用函数执行了");
    // },[])

    // 添加特定依赖项 初始+依赖项更新
    // useEffect(() => {
    //     console.log("副作用函数执行了");
    // },[count])

    

    // 清除副作用
    const [show,setShow] = useState(true)
    // 自定义组件
    function Test(){
        useEffect(() => {
            console.log("副作用函数执行了");
            const timer = setInterval(() => {
                console.log("开启定时器");
            })
            // 清除副作用(组件卸载执行)
            return () => {
                clearInterval(timer)
            }
        },[])
        return <div>测试清除副作用操作</div>
    }
   
    return (
        <div>
           测试数据
           {show && <button onClick={() => setCount(count + 1)}>修改count依赖项:{count}</button>}
           {show && <Test/>}
           <button onClick={() => setShow(!show)}>组件挂起与卸载(组件更新)</button>
        </div>
    )
}

11、自定义hook函数

在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook

以use开头的函数,通过自定义hook函数可以用来实现逻辑的封装与复用

使用规则

  1. 只能在组件中或者其他自定义hook函数中调用
  2. 只能在组件的顶层调用,不能嵌套在if、for、其他函数中

步骤

  1. 编写一个use开头的函数
  2. 在函数内部编写封装的逻辑
  3. return出去组件中用到的状态和方法
  4. 组件中调用函数解构赋值使用
"use client"
import { useState } from "react"

// 自定义hook函数
function useToggle(){
    const [value,setValue] = useState(true)
    const toggle = () => {
        setValue(!value)
    }
    return {
        value,
        toggle
    }
}

export default function Hook(){
    const {value,toggle} = useToggle()
    return (
        <div>
            {value && <div>控制显示隐藏</div>}
            {/* <button onClick={() => setValue(!value)}>切换按钮</button> */}
            <button onClick={toggle}>切换按钮</button>
        </div>
    )
}

12、图片使用 Image 

13、缓存计算结果 useMemo 

定义tsx文件

"use client"

import { Dispatch, ReactNode, SetStateAction,createContext, useContext, useMemo, useState } from "react"

type State = {
    displayNavigation: boolean
}

type AppContextProps = {
    state: State,
    setState: Dispatch<SetStateAction<State>>
}

const AppContext = createContext<AppContextProps>(null!)

export function useAppContext(){
    return useContext(AppContext)
}

export default function AppContextProvider({
    children
}:{
    children: ReactNode
}){
    const [test,setTest] = useState(false)
    console.log("AppContextProvider 重新渲染");
    
    const [state,setState] = useState({displayNavigation:true})

    // 使用缓存计算
    const contextValue = useMemo(() => {
        return {state,setState}
    },[state,setState])
    return (
        <AppContext.Provider value={contextValue}>
            <button className="bg-blue-600 p-3 text-white" onClick={() => setTest((v) => !v)}>useMemo使用按钮测试</button>
            {children}
        </AppContext.Provider>
    )
    // return <AppContext.Provider value={{state,setState}}>{children}</AppContext.Provider>
}

在layout.tsx注入

import AppContextProvider from "../components/AppContext";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
    <body className="dark">
      layout:
      <AppContextProvider>AppContextProvider: {children}</AppContextProvider>
    </body>
  </html>
  );
}

 定义测试文件

"use client"
import { useAppContext } from "../AppContext";

export default function Navigation() {
  const {
    state: { displayNavigation }
  } = useAppContext();
  console.log("Navigation 重新渲染");
  
  return (
    <nav className={`${displayNavigation ? "" : "hidden"} h-full w-[260px] bg-gray-900 text-gray-300 p-2`}>
     测试useMemo
    </nav>
  );
}

14、Icons

npm i react-icons
  • 创建自定义组件文件 components/about/myButton.tsx 【注意:自定义组件首字母大写】
  • 导入自定义组件MyButton,测试文件testButton.tsx
import { ComponentPropsWithoutRef } from "react";
// 引入图标类型
import { IconType } from "react-icons"
// 定义ts类型
type MyButtonType = {
    icon?: IconType,
    variant?: "default" | "outline" | "text"
}  & ComponentPropsWithoutRef<"button">
// 自定义button 悬浮 激活样式
// export default function MyButton({
//     children,
//     className,
//     ...props
// }:ComponentPropsWithoutRef<"button">){
//     return (
//         <button className={`border border-gray-600 rounded px-3 py-1.5 hover:bg-gray-800 active:bg-gray-700 ${className}`} {...props}>
//             {children}
//         </button>
//     )
// }

export default function MyButton({
    icon: Icon,
    children,
    className = "",
    variant = "default",
    ...props
}:MyButtonType){
    return (
        <button 
        className={`inline-flex items-center min-w-[38px] min-h-[38px] rounded p-2 bg-red-900
        ${variant === "default" ? "border border-red-900 bg-red-900" : "border-blue-900 bg-blue-900"} 
        ${className}`}
         {...props}
         >
           {Icon && <Icon className={`text-lg ${children ? "mr-1" : ""}`}></Icon>}
           {children}
        </button>
    )
}
import MyButton from "../about/myButton";
// 导入图标
import { HiPlus } from "react-icons/hi"
import { LuPanelLeft } from "react-icons/lu"

export default function TestButton(){
    return <nav className="h-full w-[260px] bg-gray-900 text-gray-300 p-2">
        <MyButton icon={HiPlus} variant="outline" className="flex-1">新建对话</MyButton>
        <MyButton icon={LuPanelLeft}></MyButton>
    </nav>
}

 JavaScript库推荐

className

字符拼接,不够直观,推荐使用 classnames库

Lodash

使JavaScript更容易处理数组、数字、对象、字符串等。lodash

dayjs

解析、验证、操作和显示现代浏览器的日期和时间 dayjs

uuid

创建随机的uuid uuid

// 导入classnames
import classNames from 'classnames'
// lodash
import _ from 'lodash'

// h-font 默认类名样式 active 动态样式
// className={typeTab === item.type ? "active" : ""}
   className={`h-font ${typeTab === item.type && 'active'}`}

// 使用js库
   className={classNames('h-font',{active:typeTab === item.type})}

// 排序
const [stateList, setStateList] = useState(_.orderBy(list,'hot','desc'));

Immerjs

state 有多层的嵌套,建议使用immerjs库,它可以让你使用简便但可以直接修改的语法编写代码,并会帮你处理好复制的过程。

尝试使用 Immer

  1. 运行 npm install use-immer 添加 Immer 依赖
  2. 用 import { useImmer } from 'use-immer' 替换掉 import { useState } from 'react'
"use client"
/**
 * 由 Immer 提供的 draft 是一种特殊类型的对象,被称为 Proxy,它会记录你用它所进行的操作。这就是你能够随心所欲地直接修改对象的原因所在!
 * 从原理上说,Immer 会弄清楚 draft 对象的哪些部分被改变了,并会依照你的修改创建出一个全新的对象。
 */
import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
    }
  });

  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }

  
  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
    </>
  );
}

Redux 

React最常用的集中状态管理工具 redux官网,类似与Vue中的Pinia(Vuex),可以独立于框架运行

作用:通过集中管理的方式管理应用的状态

独立于框架运行(html) 步骤

  1. 定义一个reducer函数
  2. 使用createStore方法传入reducer函数,生成一个Store实例对象
  3. 使用Store实例的subscribe方法订阅数据的变化,数据一旦变化,可以得到通知
  4. 使用Store实例的dispatch方法提交action对象触发数据变化
  5. 使用Store实例的getState方法获取最新的状态数据更新到视图中
<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>
<script src="https://cdn.bootcdn.net/ajax/libs/redux/4.2.1/redux.min.js"></script>

<script>
    // 1、定义reducer函数
    // 作用:根据不同的action对象,返回新的state
    // state:数据初始状态
    // action:修改数据
    function reducer(state = {count: 0},action){
        // 数据不可变,基于原始状态生成一个新的状态
        if(action.type === "DEC"){
            return {count: state.count - 1}
        }else if(action.type === "ADD"){
            console.log(state.count,'加1',state.count + 1);
            return {count: state.count + 1}
        }else{
            return state
        }
    }
    // 2、使用reducer函数生成store实例
    const store = Redux.createStore(reducer)

    // 3、通过store实例的subscribe订阅数据的变化
    store.subscribe(() => {
        console.log("state数据变化",store.getState());
        // 更新视图
        document.getElementById('count').innerText = store.getState().count
    })

    // 4、通过store实例的dispatch函数提交action修改状态
    // 添加按钮
    const addbtn = document.getElementById('increment')
    addbtn.addEventListener('click',() => {
        store.dispatch({
            type: "ADD"
        })
    })

    // 减号按钮
    const delbtn = document.getElementById('decrement')
    delbtn.addEventListener('click',() => {
        store.dispatch({
            type: "DEC"
        })
    })

    // 5、通过store实例的getState方法获取最新状态更新视图
</script>

Redux管理数据流程分成3个核心

  1. state:对象,存放管理的数据状态
  2. action:对象,修改数据
  3. reducer :根据action的操作生成一个新的state

react引入redux

安装依赖:Redux Toolkit and React-Redux

谷歌插件:

Redux DevTools

React Developer Tools

npm install @reduxjs/toolkit react-redux
  1. Redux Toolkit:官方推荐的编写Redux逻辑的方法,简化了书写方式,包括强大的数据获取和缓存功能【RTK查询】
  2. React-Redux:连接React和Redux的中间件

1、CRA(create-react-app):react项目  react-redux

2、CNA(create-next-app ):next项目 

创建lib/StoreProvider.tsx文件。

/*
要使用这个新的 makeStore 功能,我们需要创建一个新的“客户端”组件,
该组件将创建存储并使用React Redux Provider 组件共享存储。

任何与Redux存储交互的组件(创建、提供、读取或写入)都需要是客户端组件。
这是因为访问存储需要React上下文,而上下文仅在客户端组件中可用。

每个切片文件都应该为其初始状态值定义一个类型,这样 createSlice 就可以在每个情况下正确地推断 state 的类型。
*/
"use client";
import { useRef } from "react";
import { Provider } from "react-redux";
import { makeStore, AppStore } from "./store";
// 如果需要使用父组件的数据初始化存储,则将该数据定义为客户端 StoreProvider 组件上的道具,并在切片上使用Redux操作来设置存储中的数据
// import { initializeCount } from "./features/countStore";

export default function StoreProvider({
  // 初始化存储
  // count,
  children,
}: {
  // count: number;
  children: React.ReactNode;
}) {
  const storeRef = useRef<AppStore>();
  if (!storeRef.current) {
    // 首次渲染时创建存储实例
    storeRef.current = makeStore();
    // 初始化存储 count类型报错:【初始状态定义类型】 https://redux.js.org/tutorials/typescript-quick-start
    // storeRef.current.dispatch(initializeCount(count));
  }

  return <Provider store={storeRef.current}>{children}</Provider>;
}

创建 lib/features/countStore.ts文件

import { createSlice,PayloadAction } from '@reduxjs/toolkit'
// 导入的“RootState”类型
import type { RootState } from '../store'
// 定义初始化状态的类型
export interface CounterState {
  value: number,
  username: string
}

// 使用该类型定义初始状态
const initialState: CounterState = {
  value: 0,
  username: 'admin'
}
const counterSlice = createSlice({
  name: 'counter',
// `createSlice`将从`initialState`参数推断状态类型
  initialState,
  // 初始化state
  // initialState: {
  //   value: 0
  // },
  // 修改状态方法 同步方法
  reducers: {
    // 传参,参数会传递到action对象payload属性上
    // 使用PayloadAction类型声明`action.payload的内容`
    initializeCount: (state,action: PayloadAction<number>) => {
       console.log("chushizhi",action);
       state.value += action.payload
    },
    incremented: state => {
      state.value += 1
    },
    decremented: state => { 
      state.value -= 1
    },
    // 重置仓库【初始化】
    resetStore: () => {
      return initialState
    },
    // 修改仓库值【需替换新值】
    editUsername: (state,action: PayloadAction<string>) => {
      return {
        value: state.value,
        username: action.payload
      }
    }
  }
})

// 按需导出 解构出来的action创建的函数
export const { incremented, decremented,initializeCount,resetStore,editUsername } = counterSlice.actions

// 默认导出 获取reducer
export default counterSlice.reducer

// 其他代码(如选择器)可以使用导入的“RootState”类型
export const selectCount = (state: RootState) => state.countStore.value

创建lib/store.ts文件

import { configureStore } from '@reduxjs/toolkit'
import countStore from './features/countStore'
// makeStore:为每个请求创建一个存储实例
export const makeStore = () => {
  return configureStore({
    reducer: {
      countStore
    }
  })
}

// 推断makeStore的类型
export type AppStore = ReturnType<typeof makeStore>
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']

创建lib/whooks.ts文件

import { useDispatch, useSelector, useStore } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch, AppStore } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppStore: () => AppStore = useStore

修改layout布局文件

/* 
创建一个 app/ 文件夹,然后添加一个 layout.tsx 和 page.tsx 文件。
当用户访问应用程序的根目录时,将呈现这些内容 ( / )
在内部 app/layout.tsx 创建一个根布局,其中包含所需的 <html> 和 <body> 标签
*/
import StoreProvider from "../lib/StoreProvider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        layout:根布局
        <StoreProvider>{children}</StoreProvider>
      </body>
    </html>
  );
}

创建测试文件

"use client"; // 使用客户端渲染
import { useAppSelector, useAppDispatch } from "../../../lib/whooks";
import {
  incremented,
  decremented,
  resetStore,
  editUsername,
} from "../../../lib/features/countStore";
export default function Page() {
  // 读取仓库属性
  const username = useAppSelector((state) => state.countStore.username);
  const count = useAppSelector((state) => state.countStore.value);

  // 修改action
  const dispatch = useAppDispatch();

  function add() {
    console.log("加1");
    dispatch(incremented());
  }
  function del() {
    console.log("减1");
    dispatch(decremented());
  }

  function reset() {
    dispatch(resetStore());
  }


  return (
    <div>
      <div>username: {username}</div>
      <button id="decrement" onClick={del}>
        -
      </button>
      <span id="count">{count}</span>
      <button id="increment" onClick={add}>
        +
      </button>
      <div>
        <button onClick={reset}>重置</button>
      </div>
      <div>
        <button onClick={() => dispatch(editUsername("用户名被修改拉!"))}>修改用户</button>
      </div>
    </div>
  );
}

Tailwind

next使用Tailwind教程icon-default.png?t=N7T8https://nextjs.org/docs/pages/building-your-application/styling/tailwind-css#installing-tailwind

tailwindcssicon-default.png?t=N7T8https://www.tailwindcss.cn/

Router 

官网

Next.js有两个不同的路由器:应用程序路由器和页面路由器。

  • App Router是一款更新的路由器,允许您使用React的最新功能,如服务器组件和流媒体。
  • Pages路由器是最初的Next.js路由器,它允许您构建服务器渲染的React应用程序,并继续支持旧的Next.jsp应用程序

顶级文件夹:app目录:App Router 应用程序路由器;pages:Pages Router 页面路由器

app 目录中的页面默认为服务器组件。这与页面是客户端组件的 pages 目录不同。

App Router

一个特殊的 page.js 文件用于使管段可以公开访问。 

通过在 app 目录中添加 page.js 文件来创建您的第一个页面。

app路由约定

路由文件名文件后缀说明
layout.js / .jsx / .tsxLayout
page.js / .jsx / .tsxPage
loading.js / .jsx / .tsxLoading UI
not-found.js / .jsx / .tsxNot found UI
error.js / .jsx / .tsxError UI
global-error.js / .jsx / .tsxGlobal error UI
router.js / .tsAPI endpoint
template.js / .jsx / .tsxRe-rendered layout
default.js / .jsx / .tsxParallel route fallback page

<Link> 是一个内置组件,它扩展了HTML <a> 标记,以提供路由之间的预取和客户端导航。这是Next.js中在路线之间导航的主要方法,也是推荐的方法。

"use client";
import Link from 'next/link'
import { usePathname } from "next/navigation";
import { useRouter } from "next/navigation";
 
export default function Page() {
  // 获取当前页面路由地址
  const pathname = usePathname();
  // 使用路由跳转 【对于服务器组件,请改用 redirect 函数。】
  const router = useRouter();
  return (
    <div>
        <Link href="/dashboard">dashboard</Link>
         当前路由地址:{pathname}
          {/* 客户端组件路由跳转 */}
          <button type="button" onClick={() => router.push("/dashboard")}>
                Dashboard
          </button>
    </div>
    )
}

Pages Router 

pages路由约定

文件名文件后缀说明
_app().js / .jsx / .tsxCustom App
_document.js / .jsx / .tsxCustom Document
_error.js / .jsx / .tsxCustom Error Page
404.js / .jsx / .tsx404 Error Page
500.js / .jsx / .tsx500 Error Page
index.js / .jsx / .tsxHome page
file.js / .jsx / .tsxNested page

 React+Ant后台框架

ant-design-proicon-default.png?t=N7T8https://github.com/ant-design/ant-design-pro

Ant-Designicon-default.png?t=N7T8https://pro.ant.design/zh-CN/docs/overview

  • 11
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值