我的第一个组件
- 组件:UI 构成要素
- 定义组件
- 使用组件
umijshttps://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代码。
优势
劣势:页面切换慢,服务器压力大
next.jshttps://nextjs.org/docs/getting-started/installation
建议Node.js环境在18.17以上
npm install next@latest react@latest react-dom@latest
# 具体配置流程请查看官网
注意:用tsx文件会报错,运行npm run dev 启动开发服务器,会自动引入ts开发依赖,创建配置文件
1、创建一个 app/ 文件夹,然后添加一个 layout.tsx 和 page.tsx 文件。当用户访问应用程序的根目录时,将呈现这些内容 ( / )。推荐
// 在内部 app/layout.tsx 创建一个根布局,其中包含所需的 和 标签
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
// 创建一个 app/page.tsx 包含一些初始内容的主页
export default function Page() {
return Hello, Next.js!
}
2、如果您更喜欢使用页面路由器而不是应用程序路由器,则可以在项目的根目录下创建一个 pages/
目录。【记得删除 app/page.tsx,有冲突】 可选,不推荐
在pages/ 添加一个 _app.tsx 文件来定义全局布局。详细了解自定义应用文件。
在pages/ 添加一个 _document.tsx 文件来控制来自服务器的初始响应。详细了解自定义文档文件
// _app.tsx import type { AppProps } from 'next/app' export default function App({ Component, pageProps }: AppProps) { return
} // _document.tsx import { Html, Head, Main, NextScript } from 'next/document' export default function Document() { return ( ) } // index.tsx export default function Page() { return Hello, Next.js!
}
3、创建一个 public
文件夹来存储静态资产,例如图像、字体等。然后,代码可以从基本 URL ( ) 开始引用 public
目录中的文件 /
。 【可选】
4、运行npm run dev启动项目。【初始化已完成】
根布局必须定义 和
标记,因为 Next.js 不会自动创建它们。
默认情况下,根布局是服务器组件,不能设置为客户端组件。
建议使用 create-next-app
启动新的 Next.js 应用程序,它会自动为您设置所有内容。
npx create-next-app@latest
#npx Node.js工具命令,查找并执行后续的包命令
#create-next-app 设置 Next.js 应用程序
zh-hans.reacthttps://zh-hans.react.dev/
react.devhttps://react.dev/
renderinghttps://nextjs.org/docs/app/building-your-application/rendering
"use client"
约定来定义边界。还有一个 "use server"
约定,它告诉 React 在服务器上做一些计算工作。 创建react项目【建议直接使用nextjs创建】
npx create-react-app 你的项目名
# create-react-app (核心包),用于创建react项目
如果你的标签和 return
关键字不在同一行,则必须把它包裹在一对括号中
jsx:js和xml(html)的缩写,表示在js代码中编写html模块结构。【html声明式模板写法,js的可编程能力】
babelhttp://babeljs.io/
jsx可以通过大括号语法{}识别js中的表达式,比如常见的变量、函数调用、方法调用等等
行内样式、class类名控制
jsx可以使用原生js中的map方法遍历渲染列表
可以通过逻辑与运算符&&、三元表达式(?:)实现基础的条件渲染
事件绑定(属于客户端渲染,添加use client标识),语法:on+事件名称={方法名},遵循驼峰命名法
注意:传递自定义参数,不能直接写函数调用,改成箭头函数写法。
一个组件就是用户界面的一部分,拥有自己的逻辑和外观,组件之间可以互相嵌套,也可以多次复用【自定义组件首字母要大写,否则不起作用】
/* 页面是路由独有的 UI。您可以通过从 page.js 文件导出组件来定义页面。
使用嵌套文件夹定义路由,并使用 page.js 文件使路由可公开访问。*/
"use client"; // 绑定事件报错,添加使用客户端use client【允许在客户端(浏览器中)渲染 React 组件】
// 引入css
import './base.css'
// 引入useState
// 自定义组件首字母要大写,否则不起作用
function FirstComponent() {
return (
我的第一个组件
- 组件:UI 构成要素
- 定义组件
- 使用组件
);
}
// 变量
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 数字1;
} else if (count === 2) {
return 数字2;
}
}
export default function Page() {
// 基础事件绑定
function handleTestClick() {
alert("点击事件");
}
// 自定义参数事件绑定
function handleParamsClick(evt, params) {
console.log(evt, params);
}
return (
// 只能有一个根元素
注意:自定义组件首字母要大写,否则不起作用
{/* 调用js对象 */}
{"使用引号传递字符串"}
{/* 识别js变量 */}
{count}
{/* 函数调用 */}
{ifCont()}
{getName()}
{/* 方法调用 */}
{new Date().getDate()}
{/* 列表渲染 */}
{/* map循环那个结构 return结构 */}
{/* 注意必须添加key,作用:react框架内部使用,提升更新性能 */}
{list.map((item) => (
- {item.name}
))}
{/* 条件渲染 */}
{/* 逻辑与 && */}
{flag && 条件渲染}
{/* 三元运算 */}
{flag ? 三元运算true : 三元运算false}
{/* 基础事件绑定 */}
{/* 自定义参数事件绑定 */}
);
}
useState是一个react hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果【本质:和普通的js变量不同的是,状态变量一旦发生变化,组件视图UI也会跟着变化(数据驱动视图)】
const [thing, setThing]
。你也可以将其命名为任何你喜欢的名称,但遵照约定俗成能使跨项目合作更易理解。filter()
和 map()
这样不会直接修改原始值的方法,从原始数组生成一个新的数组【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 (
{/* 通过value属性绑定状态,通过change事件绑定状态同步的函数 */}
setInputValue(e.target.value)}>
);
}
父传子-props
props可传递任意的数据:数字、字符串、布尔值、数组、对象、函数、JSX
props是只读对象:子组件中只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改
特殊的prop children:在子组件标签中嵌套内容,父组件会自动在名为children的prop属性中接收改内容
子传父
在子组件中调用父组件中的函数并传递参数
使用状态提升实现兄弟组件通信
借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递
使用Context机制跨层级组件通信
"use client";
import { useState,createContext, useContext } from "react";
// 父传子
// 1、父组件传递数据,子组件标签身上绑定属性
// 2、子组件接收数据,props
// 子组件
function Son(props) {
// props: 对象里面包含了父组件传递过来的所有的数据
// console.log(props,'参数');
return Son Component, {props.name};
}
// 特殊的prop children
function Child(props) {
// console.log(props,'参数');
return Child Component, {props.children};
}
// 子传父
function SonParams({ onGetSonMsg }) {
const sonMsg = "son msg";
return (
Son Component
);
}
// 使用状态提升实现兄弟组件通信
function AState({ onGetAName }) {
const name = "A component"
return (
A组件---------
);
}
function BState({ name }) {
return B Component---------父传子{name};
}
// 使用Context机制跨层级组件通信
const getContext = createContext("Context默认值")
function A(){
return (
Context A
)
}
function B(){
const msg = useContext(getContext)
return (
Context B,{msg}
)
}
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 (
);
}
用于在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 测试清除副作用操作
}
return (
测试数据
{show && }
{show && }
)
}
在 React 中,useState
以及任何其他以“use
”开头的函数都被称为 Hook。
以use开头的函数,通过自定义hook函数可以用来实现逻辑的封装与复用
使用规则
步骤
"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 (
{value && 控制显示隐藏}
{/* */}
)
}
定义tsx文件
"use client"
import { Dispatch, ReactNode, SetStateAction,createContext, useContext, useMemo, useState } from "react"
type State = {
displayNavigation: boolean
}
type AppContextProps = {
state: State,
setState: Dispatch>
}
const AppContext = createContext(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 (
{children}
)
// return {children}
}
在layout.tsx注入
import AppContextProvider from "../components/AppContext";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
layout:
AppContextProvider: {children}
);
}
定义测试文件
"use client"
import { useAppContext } from "../AppContext";
export default function Navigation() {
const {
state: { displayNavigation }
} = useAppContext();
console.log("Navigation 重新渲染");
return (
);
}
npm i react-icons
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 (
//
// )
// }
export default function MyButton({
icon: Icon,
children,
className = "",
variant = "default",
...props
}:MyButtonType){
return (
)
}
import MyButton from "../about/myButton";
// 导入图标
import { HiPlus } from "react-icons/hi"
import { LuPanelLeft } from "react-icons/lu"
export default function TestButton(){
return
}
字符拼接,不够直观,推荐使用 classnames库
使JavaScript更容易处理数组、数字、对象、字符串等。lodash
解析、验证、操作和显示现代浏览器的日期和时间 dayjs
创建随机的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'));
state 有多层的嵌套,建议使用immerjs库,它可以让你使用简便但可以直接修改的语法编写代码,并会帮你处理好复制的过程。
尝试使用 Immer
npm install use-immer
添加 Immer 依赖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 (
<>
{person.artwork.title}
{' by '}
{person.name}
(located in {person.artwork.city})
>
);
}
React最常用的集中状态管理工具 redux官网,类似与Vue中的Pinia(Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态
独立于框架运行(html) 步骤
- 定义一个reducer函数
- 使用createStore方法传入reducer函数,生成一个Store实例对象
- 使用Store实例的subscribe方法订阅数据的变化,数据一旦变化,可以得到通知
- 使用Store实例的dispatch方法提交action对象触发数据变化
- 使用Store实例的getState方法获取最新的状态数据更新到视图中
0
Redux管理数据流程分成3个核心
安装依赖:Redux Toolkit and React-Redux
谷歌插件:
Redux DevTools
React Developer Tools
npm install @reduxjs/toolkit 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();
if (!storeRef.current) {
// 首次渲染时创建存储实例
storeRef.current = makeStore();
// 初始化存储 count类型报错:【初始状态定义类型】 https://redux.js.org/tutorials/typescript-quick-start
// storeRef.current.dispatch(initializeCount(count));
}
return {children} ;
}
创建 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) => {
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) => {
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
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType
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 = useSelector
export const useAppStore: () => AppStore = useStore
修改layout布局文件
/*
创建一个 app/ 文件夹,然后添加一个 layout.tsx 和 page.tsx 文件。
当用户访问应用程序的根目录时,将呈现这些内容 ( / )
在内部 app/layout.tsx 创建一个根布局,其中包含所需的 和 标签
*/
import StoreProvider from "../lib/StoreProvider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
layout:根布局
{children}
);
}
创建测试文件
"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 (
username: {username}
{count}
);
}
next使用Tailwind教程https://nextjs.org/docs/pages/building-your-application/styling/tailwind-css#installing-tailwind
tailwindcsshttps://www.tailwindcss.cn/
官网
Next.js有两个不同的路由器:应用程序路由器和页面路由器。
顶级文件夹:app目录:App Router 应用程序路由器;pages:Pages Router 页面路由器
app
目录中的页面默认为服务器组件。这与页面是客户端组件的 pages
目录不同。
一个特殊的 page.js
文件用于使管段可以公开访问。
通过在 app
目录中添加 page.js
文件来创建您的第一个页面。
app路由约定
路由文件名 | 文件后缀 | 说明 |
---|---|---|
layout | .js / .jsx / .tsx | Layout |
page | .js / .jsx / .tsx | Page |
loading | .js / .jsx / .tsx | Loading UI |
not-found | .js / .jsx / .tsx | Not found UI |
error | .js / .jsx / .tsx | Error UI |
global-error | .js / .jsx / .tsx | Global error UI |
router | .js / .ts | API endpoint |
template | .js / .jsx / .tsx | Re-rendered layout |
default | .js / .jsx / .tsx | Parallel route fallback page |
是一个内置组件,它扩展了HTML
标记,以提供路由之间的预取和客户端导航。这是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 (
dashboard
当前路由地址:{pathname}
{/* 客户端组件路由跳转 */}
)
}
pages路由约定
文件名 | 文件后缀 | 说明 |
---|---|---|
_app() | .js / .jsx / .tsx | Custom App |
_document | .js / .jsx / .tsx | Custom Document |
_error | .js / .jsx / .tsx | Custom Error Page |
404 | .js / .jsx / .tsx | 404 Error Page |
500 | .js / .jsx / .tsx | 500 Error Page |
index | .js / .jsx / .tsx | Home page |
file | .js / .jsx / .tsx | Nested page |
Ant-Designhttps://pro.ant.design/zh-CN/docs/overview