react
react
React入门到实战(2022全网最新)_
我在b站看过的react的视频,学习的视频地址为:这个视频是当时刚看完vue2的时候,顺道看的,由于做的都是有关vue的项目,没怎么用过react,这个视频可以让我对react有点基本了解吧。
技术栈为:React + Hook + React-router-v6 + Mobx + AntD
源码地址:https://gitee.com/react-cp/react-pc-code
React基础讲义: https://www.yuque.com/fechaichai/qeamqf/xbai87
React和Mobx讲义: https://www.yuque.com/fechaichai/qeamqf/apomum
ReactPc项目讲义: https://www.yuque.com/fechaichai/tzzlh1
我跟着视频写的源码放在了码云上了,项目大概写了一半。 react_lianxi_jikeyuan: react练习项目-极客园 (gitee.com)
vite创建react项目
https://juejin.cn/post/7240838046789812282?searchId=20240111164719A152F422F9A9C8A6FEFD#heading-11
企业级前端工程化配置指南https://segmentfault.com/a/1190000044458156#item-6
npm init vite@latest
按照vite创建项目流程,选择react配置即可创建react项目
一文搞定react-router-dom最新版V6路由的入门及使用_react-router-domv6-CSDN博客
创建路由(方法1)
npm install react-router-dom
在App.jsx中引入路由
// 引入组件
import About from './views/About'
import List from './views/List'
import Success from "./views/Success"
// 引入路由相关内容 BrowserRouter配置路由模式(最好设置在App跟组件上) Routes用设置路由出口 Route写在Routes内部,用于配置和匹配路由的路径和组件
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
function App() {
return (
<div className="App">
<BrowserRouter>
<Link to="/about">About</Link>
<Link to="/list">List</Link>
<Link to="/success">Success</Link>
<Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/list" element={<List />}></Route>
<Route path="/success" element={<Success />}></Route>
<Route path="/success/:ids" element={<Success />}></Route>
</Routes>
<h1>Vite + React</h1>
<div>
有Link的,一定要有Route和其匹配
</div>
</BrowserRouter>
</div>
)
}
export default App
About.jsx (路由组件)
// useNavigate 是用于路由函数式跳转的
import { Routes, Route, Link,useNavigate } from 'react-router-dom'
function About() {
const navigate = useNavigate()
return (
<div className="App">
{/* 这里的'/success'不需要route,因为在App.jsx跟组件中有配置 navigate传参如下所示 */}
<button onClick={()=>{navigate('/success',{replace:false})}}>go</button>
<Link to="/success">about to the success</Link>
<button onClick={()=>{navigate('/success/123456789')}}>go the success2</button>
</div>
)
}
export default About
路由参数获取
Success.jsx (获取参数)
当路由和传参是以下路径时,用 useSearchParams 来获取参数
<Route path="/success?id=1234567" element={<Success />}></Route>
<button onClick={()=>{navigate('/success',{replace:false})}}>go</button>
当路由和传参是以下路径时,用 useParams 来获取参数
<Route path="/success/:ids" element={<Success />}></Route>
<button onClick={()=>{navigate('/success/123456789')}}>go the success2</button>
// useSearchParams 用于获取路由跳转传递的参数
import { useSearchParams ,useParams} from 'react-router-dom'
function Success() {
const [params]=useSearchParams()
const id=params.get('id')
const paramsUse=useParams()
let ids=paramsUse.ids
return (
<div className="App">
<div>success</div>
<div>id {id}</div>
<div>ids {ids}</div>
</div>
)
}
export default Success
创建路由(方法2)
参考:浅析React Router V6 useRoutes的使用-CSDN博客
import { BrowserRouter as Router, useRoutes } from 'react-router-dom';
import About from './About';
import Home from './Home';
const MyRoutes = () => {
return useRoutes([
{
path: '/',
element: <Home />,
},
{
path: '/home',
element: <Home />,
},
{
path: '/about',
element: <About />
},
]);
};
function App() {
return (
<div>
<Router>
<MyRoutes />
</Router>
</div>
);
}
export default App;
引入 ant design
在 Vite 中使用 - Ant Design (antgroup.com)
npm install antd --save / yarn add antd / cnpm install antd --save
引入 Antd的css(全局样式) 没有引入样式,样式也存在(非必要项)
@import 'antd/dist/antd.css';
jsx组件内使用(先引入,再使用)
import { Button } from 'antd';
<Button type="primary">Primary</Button>
组件中文配置
App 组件外包裹一层 ConfigProvider
import { ConfigProvider } from 'antd';
import zhCN from 'antd/es/locale/zh_CN';
return (
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
);
next创建react项目
创建项目命令
npx create-next-app@latest
应用路由、页面路由
应用路由属于较新的路由,这里使用的是应用路由。
"use client"
next项目中使用 useState 需要在组件顶部加上 "use client";
在app
目录下,Next.js默认使用 Server Components ,
您的组件或其父组件之一应该在顶部有 "use client"
。这样,它就变成了 Client Component ,一个“普通”的 React 组件
javascript - 您正在导入一个需要 useState 的组件。它仅在客户端组件中有效,但其父组件均未标记为 "use client" - IT工具网 (coder.work)
页面跳转
import Link from 'next/link'
<Link href="/about">about</Link>
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
umi 创建项目
https://umijs.org/docs/guides/getting-started
组件
图片展示
import imgUrl from './assets/MyLogo.jpg'
<img src={imgUrl} style={{width:'100px',height:'100px'}}/>
src里面直接写图片路径显示不出来
父组件调用子组件
参考: react v-18父组件调用子组件的方法和数据_react18 父组件调用子组件方法-CSDN博客
常见问题:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
发生这种情况是因为默认情况下,React 不允许组件访问其他组件的 DOM 节点。甚至自己的子组件也不行!这是故意的。Refs 是一种脱围机制,应该谨慎使用。手动操作 另一个 组件的 DOM 节点会使你的代码更加脆弱。
相反,想要 暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件。下面是如何使用 forwardRef
API:
子组件
子组件 (通过useImperativeHandle() 抛出)
import React, { useImperativeHandle } from "react";
// 注意 props 和 ref 是通过 React.forwardRef 传递的
// eslint-disable-next-line react/display-name
const TestThing =React.forwardRef((props ,ref) => {
useImperativeHandle(
ref,
() => ({ num,setTheNum }) //父组件通过ref获取值,要在这里抛出
);
const [num, setNum] = useState(5)
const setTheNum=(addNum)=>{
setNum(num+addNum)
}
return (
<div ref={ref}>
{num}
</div>
)
})
export default TestThing
父组件
import { useRef } from 'react';
import TestThing from './components/TestThing/index.jsx'
function App () {
// ref 通过 ref 绑定子组件
const TestThingRef = useRef(null);
const clickComp=()=>{
// TestThingRef.current 获取子组件抛出的数据和方法
console.log(TestThingRef.current)
TestThingRef.current.setTheNum(2)
}
return (
<div className="App">
<div>
<TestThing ref={TestThingRef}></TestThing>
<button onClick={clickComp}>新增</button>
</div>
</div>
)
}
export default App
redux使用
一文让你彻底弄懂Redux的基本原理以及其如何在React中使用! - 掘金 (juejin.cn)
依赖下载
在React中使用redux,官方建议安装两个其他插件 - Redux Toolkit 和 React-Redux
Redux Toolkit(RTK)
:官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式React-Redux
:用来 链接 Redux 和 React 组件的中间件
npm install @reduxjs/toolkit react-redux
store/modules/counterStore.js
import { createSlice } from '@reduxjs/toolkit'
const counter = createSlice({
// 模块名称独一无二
name: 'counter',
// 初始数据
initialState: {
count: 5
},
// 修改数据的同步方法
reducers: {
// 不传参修改
add (state) {
state.count++
},
//传参修改, action为一个对象 对象中有一个固定的属性叫做payload 为传递过来的参数
add2(state, action){
state.count=state.count+action.payload
}
}
})
const { add,add2 } = counter.actions
const counterReducer = counter.reducer
// 导出修改数据的函数
export { add ,add2}
// 导出reducer
export default counterReducer
store/index.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './modules/counterStore'
export default configureStore({
reducer: {
// 注册子模块
counter: counterReducer
}
})
入口文件中,通过Provider提供store数据
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'
ReactDOM.createRoot(document.getElementById('root')).render(
// 提供store数据
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
页面使用数据,修改操作 App.js
import { useSelector, useDispatch } from 'react-redux'
import { add,add2 } from './store/modules/counterStore'
function App () {
// 使用数据
const { count } = useSelector(state => state.counter)
// 修改数据
const dispatch = useDispatch()
const clickHandler = () => {
// 1. 生成action对象
const action = add()
// 2. 提交action进行数据更新
dispatch(action)
}
const clickHandler2=(num)=>{
console.log(num)
// 1. 生成action对象
const action2 = add2(num)
// 2. 提交action进行数据更新
dispatch(action2)
}
return (
<div className="App">
{count}
{/* 不传参修改count的值 */}
<button onClick={clickHandler}>+</button>
{/* 传参修改count的值 */}
<button onClick={()=>{clickHandler2(3)}}>+3</button>
</div>
)
}
export default App
axios
React 项目中引入 Axios | Axios 中文文档 (jsnoteclub.com)
npm install axios
简易封装:request.js
import axios from "axios"
// 创建一个 Axios 实例
const request = axios.create({
baseURL: "http://1.94.16.149:8084", // 设置基本URL
timeout: 5000, // 设置超时时间
// headers: { Authorization: "Bearer " + token } // 设置请求头
})
// 添加请求拦截器
request.interceptors.request.use(
(config) => {
// 在发送请求之前做一些处理,比如添加loading效果
return config
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 添加响应拦截器
request.interceptors.response.use(
(response) => {
// 对响应数据做一些处理,比如解析响应结果
return response.data
},
(error) => {
// 对响应错误做些什么
return Promise.reject(error)
}
)
export default request
使用
import {useState, useEffect } from 'react';
import request from './utils/request.js';
function App () {
const [responseData, setResponseData] = useState(null);
const [error, setError] = useState(null);
const getList=()=>{
request.get('/classes/page/list').then(res=>{
console.log(res.data.records)
setResponseData(res.data.records)
}).catch(error => {
// 处理错误
setError(error);
});
}
useEffect(() => {
getList()
}, []); // 添加依赖以确保只在组件挂载时发起请求
return (
<div className="App">
<div>
{
responseData && responseData.map(item => (
<div key={item.id}>{item.className}-{item.classDesc}</div>
))
}
</div>
</div>
)
}
export default App
Hooks
【React Hooks】掌握及对比常用的8个Hooks(优化及使用场景) - 掘金 (juejin.cn)
一篇文章带你理解 React 中最“臭名昭著”的 useMemo 和 useCallback
useState
const [state, setState] = useState(initialState) // initialState 初始值
修改对象&&数组的值: React函数组件状态Hook—useState《进阶-对象&&数组》_react usestate 对象数组-CSDN博客
需要注意 setState 是异步操作
useRef
通过 ref 操作 DOM (参考父组件调用子组件)
React 将会把 DOM 节点设置为 ref 对象的 current
属性。现在可以借助 ref 对象访问 <input>
的 DOM 节点
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
return <input ref={inputRef} />;
}
function handleClick() {
inputRef.current.focus();
}
无法获取自定义组件的 ref
默认情况下,自定义组件不会暴露它们内部 DOM 节点的 ref。
需要将自定义组件包装在 forwardRef
里
import { forwardRef } from 'react';
const MyInput = forwardRef(({ value, onChange }, ref) => {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
});
export default MyInput;
useEffect
1. 不添加依赖项
组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
useEffect(()=>{
console.log('副作用执行了')
})
2. 添加空数组
组件只在首次渲染时执行一次
useEffect(()=>{
console.log('副作用执行了')
},[])
3. 添加特定依赖项
副作用函数在首次渲染时执行,在依赖项发生变化时重新执行
useEffect(() => {
console.log('副作用执行了')
}, [count])
useMemo(缓存值)
React中的useMemo和useCallback:它们的区别及应用场景_usememo usecallback 区别-CSDN博客
useMemo是一个Hook,它接受一个函数和一个依赖数组。它返回该函数的缓存结果,并且只有当依赖项发生改变时才会重新计算。
主要用于优化性能
import React, { useImperativeHandle, useMemo } from "react";
// 注意 props 和 ref 是通过 React.forwardRef 传递的
// eslint-disable-next-line react/display-name
const TestThing = React.forwardRef((props, ref) => {
useImperativeHandle(
ref,
() => ({ num }) //父组件通过ref获取值,要在这里抛出
);
const [num, setNum] = useState(5)
const doubleNum = (num) => {
return num * 2
}
const cachedValue = useMemo(() => { return doubleNum(num) }, [num])
return (
<div>
<div ref={ref}>
{num}
<button onClick={()=>{setNum(num*2)}}>+</button>
</div>
<div>
<div>{cachedValue}</div>
</div>
</div>
)
})
useCallback(缓存函数)
useCallback也是一个Hook,它接收一个函数和一个依赖数组。它返回一个缓存的函数,并且只有当依赖项发生改变时才会重新创建。
import React, { useImperativeHandle } from "react";
// 注意 props 和 ref 是通过 React.forwardRef 传递的
// eslint-disable-next-line react/display-name
const TestThing2 = React.forwardRef((props, ref) => {
useImperativeHandle(
ref,
() => ({ num }) //父组件通过ref获取值,要在这里抛出
);
const [num, setNum] = useState(5)
const doubleNum=useCallback((addNum)=>{setNum(addNum+num)},[num])
return (
<div>
<div ref={ref}>
{num}
<button onClick={()=>{doubleNum(5)}}>+</button>
</div>
</div>
)
})
export default TestThing2
useMemo和useCallback的主要区别在于它们缓存的对象类型不同。useMemo用于缓存值(可以是任何值,包括对象、函数等),而useCallback专门用于缓存函数。
useContext
使用示例:
父组件 Home.jsx
import React,{ useState,useContext } from "react";
import TestThing from "./components/TestThing/index";
export const ThingContext = React.createContext(123); // 123是默认值
const Home = () => {
const [num, setNum] = useState(10);
return (
<div >
<div
onClick={() => {
setNum(num * 2);
}}
>
{num}
</div>
{/* 子组件要修改值时,需要把修改数据的方法也通过value传递给后代 */}
<ThingContext.Provider value={{num,setNum}}>
<TestThing></TestThing>
<Asp></Asp>
</ThingContext.Provider>
</div>
);
};
const Asp=()=>{
const {num,setNum}=useContext(ThingContext)
return (
<div onClick={()=>{setNum(num*2)}}>
{num}
</div>
)
}
export default Home;
子组件 TestThing.jsx
import { useContext } from "react";
import {ThingContext} from '../../Home.jsx' // 从父组件获取的 createContext()
// eslint-disable-next-line react/display-name
const TestThing= (() => {
const {num,setNum}=useContext(ThingContext)
return (
<div onClick={()=>{setNum(num*2)}}>
{num}
</div>
)
})
export default TestThing
useReducer
import { useReducer } from 'react';
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
throw new Error();
}
}
function TestThing2() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<>
<div>Count: {state.count}</div>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
</>
);
}
export default TestThing2
简易的增删改查示例
可参考: https://gitee.com/kong_yiji_and_lavmi/react-ant-admin/blob/vite/src/pages/list/search.tsx
https://blog.csdn.net/CuiCui_web/article/details/107108677
App.jsx 表格页
import { useState, useEffect, useRef } from "react";
import request from "./utils/request.js"; // 封装 axios 请求
import { Button } from "antd";
import AddOrUpdate from "./components/AddOrUpdate/index.jsx";
import { Table ,Pagination,Input} from "antd";
function App() {
const columns = [
{
title: "id",
dataIndex: "id",
key: "id",
},
{
title: "班级名",
dataIndex: "className",
key: "className",
},
{
title: "班级描述",
dataIndex: "classDesc",
key: "classDesc",
},
{
title: "操作",
key: "operation",
fixed: "right",
width: 100,
render: (_, record) => (
<>
<a
onClick={() => {
editIt(record);
}}
>
修改
</a>
<a
style={{ marginLeft: "10px" }}
onClick={() => {
delIt(record);
}}
>
删除
</a>
</>
),
},
];
const [queryParam,setQueryParam]=useState({
pageNum: 1,
pageSize: 5,
});
const [total,setTotal]=useState(0)
const [responseData, setResponseData] = useState(null);
const [error, setError] = useState(null);
const getList = () => {
request
.get("/classes/page/list",{params:queryParam})
.then((res) => {
// console.log(res.data.records);
setResponseData(res.data.records);
setTotal(res.data.total)
})
.catch((error) => {
// 处理错误
setError(error);
});
};
const pageChange=(page, pageSize)=>{
console.log(page,pageSize)
setQueryParam({
...queryParam,
pageNum: page,
pageSize: pageSize,
})
// React的状态更新是异步的, 分页改变时,需要用 useEffect 来监听调用 getList 方法
// console.log(queryParam) 由于状态更新是异步的,此处打印的还是原来的值
// getList()
}
const search=()=>{
setQueryParam({
...queryParam,
pageNum: 1,
})
}
const resetList=()=>{
setQueryParam({
pageNum: 1,
pageSize: 5,
})
}
useEffect(() => {
getList()
}, [queryParam]);
const [formTitle, setFormTitle] = useState("");
const editIt = (record) => {
console.log("editIt", record);
AddOrUpdateRef.current.setIsModalOpen(true);
AddOrUpdateRef.current.form.resetFields();
AddOrUpdateRef.current.form.setFieldsValue(record);
if (record.id) {
AddOrUpdateRef.current.setFormId(record.id);
}
setFormTitle("修改");
};
const addIt = () => {
AddOrUpdateRef.current.setIsModalOpen(true);
AddOrUpdateRef.current.form.resetFields();
AddOrUpdateRef.current.form.setFieldsValue({});
AddOrUpdateRef.current.setFormId("");
setFormTitle("新增");
};
const delIt = (record) => {
request.get(`/classes/del`, { params: { ids: record.id } }).then((res) => {
if (res.code == 200) {
getList();
}
});
};
useEffect(() => {
getList();
}, []); // 添加依赖以确保只在组件挂载时发起请求
const handleInputChange=(event) =>{
const { name, value } = event.target;
console.log("name",name,value)
setQueryParam(prevState => ({
...prevState,
pageNum:1,
[name]: value
}));
}
// ref 通过 ref 绑定子组件
const AddOrUpdateRef = useRef(null);
return (
<div className="App">
<div>
<div>
<span>班级名:</span>
<Input placeholder="请输入班级名" name='className' allowClear={true} value={queryParam.className} onChange={handleInputChange} style={{width:'200px'}}/>
<Button type="primary" onClick={search} style={{margin:'0 10px '}}>
查询
</Button>
<Button onClick={resetList}>
重置
</Button>
</div>
<div style={{margin:'10px 0'}}>
<Button type="primary" onClick={addIt}>
新增
</Button>
</div>
<div>
<Table
dataSource={responseData}
columns={columns}
rowKey={(record) => record.id}
pagination={false}
style={{marginBottom:'10px'}}
/>
{/* 没有使用Table自带的分页器 */}
<Pagination showQuickJumper showSizeChanger current={queryParam.pageNum} pageSize={queryParam.pageSize}
total={total} onChange={pageChange} showTotal={(total) => `共 ${total} 条`} />
</div>
</div>
<div>
<AddOrUpdate
ref={AddOrUpdateRef}
formTitle={formTitle}
onRefreshTable={getList}
></AddOrUpdate>
</div>
</div>
);
}
export default App;
AddOrUpdate.jsx 表单弹框
import React, { useState, useImperativeHandle } from "react";
import { Button, Modal, Form, Input} from "antd";
import request from "../../utils/request";
// 注意 props 和 ref 是通过 React.forwardRef 传递的
// eslint-disable-next-line react/display-name
const AddOrUpdate = React.forwardRef((props, ref) => {
useImperativeHandle(
ref,
() => ({ isModalOpen, setIsModalOpen, form, formId, setFormId })
//父组件通过ref获取值,要在这里抛出
);
const [isModalOpen, setIsModalOpen] = useState(false);
const [formId, setFormId] = useState("");
const [form] = Form.useForm();
const handleOk = () => {
setIsModalOpen(false);
console.log("form", form.getFieldsValue());
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {
console.log("finish", values);
console.log("formId", formId);
console.log("finishLast", { ...form.getFieldsValue(), id: formId });
if (formId) {
request
.post("/classes/edit", { ...form.getFieldsValue(), id: formId })
.then((res) => {
console.log(res.data);
if (res.code == 200) {
setIsModalOpen(false);
props.onRefreshTable();
}
});
} else {
request.post("/classes/add", form.getFieldsValue()).then((res) => {
console.log(res.data);
if (res.code == 200) {
setIsModalOpen(false);
props.onRefreshTable();
}
});
}
};
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
return (
<>
<Modal
title={props.formTitle}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
autoComplete="off"
footer={null}
>
<Form
{...layout}
form={form}
name="control-hooks"
onFinish={onFinish}
style={{
maxWidth: 600,
}}
>
{/* <Form.Item
name="id"
label="id"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item> */}
<Form.Item
name="className"
label="班级名"
rules={[
{
required: true,
message: "班级名不能为空",
},
]}
>
<Input />
</Form.Item>
<Form.Item name="classDesc" label="班级描述">
<Input />
</Form.Item>
<Form.Item
wrapperCol={{
offset: 8,
span: 16,
}}
>
<Button onClick={handleCancel} style={{marginRight:'10px'}}>
取消
</Button>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</Modal>
</>
);
});
export default AddOrUpdate;