一、創(chuàng)建項(xiàng)目
第一種、使用npx命令創(chuàng)建項(xiàng)目(CRA)
// 臨時(shí)下載并運(yùn)行 create-react-app,創(chuàng)建名為 my-react-app 的項(xiàng)目
npx create-react-app my-react-app
//啟動(dòng) 進(jìn)入項(xiàng)目根目錄
npm run start
- npx 是 npm 5.2.0(對應(yīng)node8.1.0) 及以上版本自帶的工具,無需單獨(dú)安裝
第二種、使用vite命令創(chuàng)建項(xiàng)目,推薦
//全局安裝 Vite 腳手架(僅需一次)
npm install -g create-vite
//node.js版本 需要 ≥ 16.0.0(推薦 LTS 版本,如 v20.x)
//my-react-app:你的項(xiàng)目名稱(可自定義,如 react-demo)
// --template react:指定模板為 React(純 JS 版本);如果需要 TS 版本,替換為 react-ts
npm create vite@latest my-react-app -- --template react
//啟動(dòng) 進(jìn)入項(xiàng)目根目錄
npm run dev
第三種、用 npx 執(zhí)行 Vite 命令創(chuàng)建項(xiàng)目(相結(jié)合,推薦)
npx create-vite@latest my-react-app -- --template react-ts
//啟動(dòng) 進(jìn)入項(xiàng)目根目錄
npm run dev
| 維度 | CRA(create-react-app) | Vite |
|---|---|---|
| 底層構(gòu)建工具 | webpack | Vite(基于 esbuild/rollup) |
| 啟動(dòng)速度 | 慢(冷啟動(dòng)需幾十秒) | 極快(冷啟動(dòng)僅幾百毫秒) |
| 熱更新速度 | 慢(改代碼等幾秒刷新) | 即時(shí)(改代碼瞬間刷新) |
| 配置靈活性 | 封閉(需 eject 才能改) | 開放(vite.config.js 直接配) |
| 體積 | 打包后體積較大 | 打包體積更小、優(yōu)化更好 |
二、vscode插件
- Chinese Language
- CSS Formatter
- ES7+ React/Redux/React-Native snippets
- Prettier
- Auto Rename Tag
- Auto Close Tag
- Path IntelliSense
- npm Intellisense
- Import Cost
三、語法
1.循環(huán)
const scoreList = [
{
id: 1, // 唯一標(biāo)識(shí)ID
name: "張三", // 姓名
score: 95 // 分?jǐn)?shù)
},
{
id: 2,
name: "李四",
score: 88
},
];
<ul>
{scoreList.map(item=><li key={item.id}>{item.name}:{item.score}</li>)}
</ul>
2.條件判斷
const isLogin=false;
<div>{isLogin && <span>已登錄</span>}</div>
<div>{isLogin?<span>已登錄</span>:<span>未登錄</span>}</div>
3.綁定事件
function bindEvent(name,e){
console.log(name+'綁定點(diǎn)擊事件',e)
}
<button onClick={(e)=>bindEvent('jack',e)}>按鈕</button>
4.引用組件
const CustomComponent=()=>{
return <ul>
{scoreList.map(item=><li key={item.id}>{item.name}:{item.score}</li>)}
</ul>
}
<CustomComponent></CustomComponent>
5.useState
類似vue3的ref和reactive,定義普通變量修改后組件種不會(huì)更新,只有通過useState定義的變量才可以更新頁面數(shù)據(jù)
// 解構(gòu)賦值:[狀態(tài)變量, 更新狀態(tài)的方法] = useState(初始值)
const [state, setState] = useState(initialValue);
- state:當(dāng)前的狀態(tài)值(可讀);
- setState:更新狀態(tài)的函數(shù)(可寫),調(diào)用后會(huì)觸發(fā)組件重渲染;
- initialValue:狀態(tài)的初始值(可以是任意類型:數(shù)字、字符串、對象、數(shù)組等)。
function App() {
const [score, setScore] = useState(10);//這句話要放在函數(shù)的最頂層,否則報(bào)錯(cuò),也不能放外面,也報(bào)錯(cuò)
const scorePlus=()=>{
setScore(score + 10);
}
return (
<div className="App">
<div>{score}</div>
<button onClick={(e)=>{scorePlus()}}>分?jǐn)?shù)自增</button>
</div>
);
}
state里面的值發(fā)生變化,組件會(huì)重新渲染
function Son (){
//因?yàn)閟tate里面的num發(fā)生變化,所以Son組件重新渲染,所以會(huì)開多個(gè)定時(shí)器
console.log('重新渲染了')
const [num,setNum] = useState(0)
let timer = setInterval(()=>{
setNum(num+1);
},1000);
return <i>this is son page:{num}</i>
}
6.使用lodash和classNames插件
import _orderBy from 'lodash/orderBy';
import _remove from 'lodash/remove';
import classNames from "classnames";
setCommentList(_orderBy(commentList,'like','desc'))
className={classNames('aaa',{'active':item.key==curTab})}
7.input雙向綁定
const [value,setValue] = useState('11');
<input value={value} onChange={(e)=>setValue(e.target.value)}/>
8.獲取dom
import {useRef } from "react";
const inputRef = useRef(null);
<input ref={inputRef} />
console.log(inputRef.current) //通過inputRef.current可以拿到dom對象,inputRef.current.focus();
9.組件傳參
- 父傳子
//子組件
function Child(props){
console.log(props) // {name:'',age:12......}
return (
<div>
{props.name}
{props.age}
{props.child}
</div>
)
}
const name = 'his name is jack';
const age = 12;
<Child
name={'jack'}
age={12}
isGo={false}
list={['1','2']}
obj={{name:'xiaoming'}}
child={<div>child node</div>}
></Child>
- 子傳父
function Son({onGetMsg}){
const msg = 'hello world';
//注意這里寫法,用箭頭函數(shù),否則會(huì)直接執(zhí)行
return <button onClick={()=>onGetMsg(msg)}>傳參給父組件</button>
}
function App() {
const [msg,setMsg] = useState('默認(rèn)值');
function getMsg(msg){
setMsg(msg)
}
return (
<div className="App">
<h4>{msg}</h4>
<Son onGetMsg={getMsg}></Son>
</div>
);
}
10.插槽
- 默認(rèn)插槽
function Son(props){
console.log(props)//props.children
return <div> {props.children}</div>
}
function App() {
return (
<div className="App">
<Son>
<i>我是插槽</i>
</Son>
</div>
);
}
11.給下級(jí)組件注入數(shù)據(jù),類似vue的provide和inject
import {createContext,useContext} from 'react'
const ctx = createContext();
function Son2(){
const msg= useContext(ctx)
console.log(msg) // hello world
return <div>son2</div>
}
function Son1(){
return <Son2></Son2>
}
function App() {
return (
<div className="App">
<ctx.Provider value={'hello world'}>
<Son1></Son1>
</ctx.Provider>
</div>
);
}
12.useEffect 類似vue的watch
useEffect(()=>{},參數(shù)2)
- 參數(shù)2為空時(shí),頁面初始化和更新都會(huì)執(zhí)行
- 為空數(shù)組[]時(shí),只有頁面初始化會(huì)執(zhí)行
- 為空數(shù)組[msg]時(shí),只有頁面初始化和msg變化時(shí)會(huì)執(zhí)行
const [msg,setMsg] = useState('');
const [count,setCount] = useState(0);
useEffect(()=>{
console.log('副作用被執(zhí)行了')
return ()=>{
/*useEffect 的 return 函數(shù)(也叫 “清理函數(shù) / 清除函數(shù)”)是 React 提供的副作用清理機(jī)制,核心作用是:在組件卸載時(shí)、或者當(dāng)前 useEffect 下次執(zhí)行前,清理掉該 useEffect 中創(chuàng)建的副作用(比如定時(shí)器、事件監(jiān)聽、網(wǎng)絡(luò)請求等),避免內(nèi)存泄漏、重復(fù)執(zhí)行等問題。*/
}
},[msg])
const inputChange = (e)=>{
setMsg(e.target.value)
}
const countPlus = (e)=>{
setCount(count+1)
}
return (
<div className="App">
<input value={msg} onChange={(e)=>{inputChange(e)}}/>
<button onClick={()=>countPlus()}>+{count}</button>
<h2>{msg}</h2>
</div>
);
useEffect 只能 return 一個(gè)【清理函數(shù)】,不能 return 任何別的東西!
// ? 錯(cuò)誤:直接 return 了異步請求
useEffect(async () => {
const res = await axios.get(...)
}, [])
因?yàn)?async 函數(shù)會(huì) 自動(dòng) return 一個(gè) Promise,React 不允許!
? 正確寫法
useEffect(() => {
const fetchData = async () => {
const res = await axios.get("xxx");
};
fetchData();
// 只有這個(gè) return 是允許的!
return () => {
console.log("組件卸載時(shí)清理");
};
}, []);
13.自定義hook鉤子函數(shù)
function useToggle(){
const [show,setShow] = useState(true)
const toggle = ()=>{
setShow(!show);
}
return{
show,
toggle,
}
}
const {show : show1,toggle : toggle1} = useToggle();
const {show : show2,toggle : toggle2} = useToggle();
return (
<div className="App">
<button onClick={()=>toggle1()}>toggle</button>
{show1 && <div>哈哈哈哈哈哈</div>}
<button onClick={()=>toggle2()}>toggle</button>
{show2 && <div>嘿嘿嘿嘿嘿嘿嘿嘿</div>}
</div>
配合接口使用
function App() {
const [commentList,setCommentList] = useState([])
useEffect(async ()=>{
async function getList(){
let {data} = await axios.get('http://localhost:10034/comments');
console.log(data)
return data;
}
let data = await getList();
setCommentList(data)
},[])
return (
<ul className="App">
{commentList.map(item=><li>
<h4>{item.username}</h4>
<p>{item.content}</p>
<p>{item.likeCount}</p>
<p>{item.publishTime}</p>
</li>)}
</ul>
);
}
四、redux
Redux Toolkit(RTK)
- 定義數(shù)據(jù) 使用createSlice 來定義數(shù)據(jù)
- 使用數(shù)據(jù) 使用useSelector來使用數(shù)據(jù)
- 修改數(shù)據(jù) 使用useDispatch來改變數(shù)據(jù)
1.安裝
npm install @reduxjs/toolkit react-redux
2.項(xiàng)目結(jié)構(gòu)
src根目錄信件store文件夾,然后將不同的模塊放入modules文件夾中,在index.js中引用

3.demo:一個(gè)login組件,一個(gè)home組件,login登錄后,home的用戶信息發(fā)生改變
分析:需要將用戶信息存入store中,方便組件同時(shí)使用數(shù)據(jù)
(1)定義:用來存放用戶信息的數(shù)據(jù)
// userinfoStore.js
import { createSlice } from "@reduxjs/toolkit";
//createSlice 用來定義數(shù)據(jù)和action的方法
const userinfoSlice = createSlice({
name:'userInfo',
initialState:{
username:'admin',
truename:'用戶名',
xzqdm:'340000',
phone:'1234567890'
},
reducers:{
// 參數(shù)兩個(gè),一個(gè)state,一個(gè)action,action.payload是傳過來的數(shù)據(jù)
setUsername(state,action){
state.username = action.payload;
},
setTruename(state,action){
state.truename = action.payload;
},
setXzqdm(state,action){
state.xzqdm = action.payload;
},
setPhone(state,action){
state.phone = action.payload;
},
}
})
//將action的方法導(dǎo)出,方便其他組件使用并且改變state數(shù)據(jù)
export const {
setUsername,
setTruename,
setXzqdm,
setPhone,
} = userinfoSlice.actions;
//導(dǎo)出,主要為了給store中的index.js使用
export default userinfoSlice.reducer;
// store/index.js
import userinfoReducer from './modules/userinfoStore'
import { configureStore } from '@reduxjs/toolkit'
//用來管理所有的數(shù)據(jù),reducer不定義的數(shù)據(jù),其他組件就訪問不到
export const store = configureStore({
reducer:{
userInfo:userinfoReducer,//組件中使用state后面的變量名用的是這里的
}
})
2.使用:通過 useSelector 來獲取值
import { useSelector } from "react-redux";
//state.userInfo和store/index.js中的reducer保持一致
const {username,truename,xzqdm,phone} = useSelector((state)=>state.userInfo);
3.修改: 通過 useDispatch 來修改值
import { useDispatch } from "react-redux";
import {setUsername,setTruename,setXzqdm,setPhone} from '../store/modules/userinfoStore'
function LoginPage(){
const dispath = useDispatch();//需要放在函數(shù)體的頂部,否則報(bào)錯(cuò)
const handleLogin = ()=>{
let userInfo = {
username:'xiaoming',
truename:'小明',
xzqdm:'341001',
phone:'1789578961'
}
// 將要修改的值傳過去
dispath(setUsername(userInfo.username));
dispath(setTruename(userInfo.truename));
dispath(setXzqdm(userInfo.xzqdm));
dispath(setPhone(userInfo.phone));
}
return(
<div>
<button onClick={handleLogin}>登錄</button>
</div>
)
}
4.在根標(biāo)簽注入store
import { Provider } from 'react-redux';
import {store} from './store/index'
root.render(
<Provider store={store}>
<App />
</Provider>
);
五.React Router 路由
npm install react-router-dom //安裝依賴
1.定義路由
import { createBrowserRouter,createHashRouter,Navigate } from 'react-router-dom'
// createBrowserRouter 這種是定義history模式路由
// createHashRouter 這種是定義hash模式路由
const router = createBrowserRouter([
{path:'/login', element:<Login/>},
{path:'home',element:<Home/>},
{//多級(jí)路由
path:'/layout',
element: <Layout/>,
children:[
//index:true 類似vue的redirect
{index:true,element:<Navigate to="/home" replace/>},
{path:'home',element:<Home/>},
],
},
])
export default router;
2.路由跳轉(zhuǎn)
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate('/home');
3.路由傳參
(1) 從地址欄取參數(shù),類似form的get
import { useSearchParams } from "react-router-dom";
{path:'user',element:<Article/>},//router中正常定義即可
navigate ('/user?id=100&name=jack');//這是傳參方式
const [params] = useSearchParams();//這是取參方式
const userId = params.get('id');
const userName = params.get('name');
<div>用戶id:{userId}</div>
<div>用戶名:{userName}</div>
(2) 隱藏參數(shù),類似form的post
import { useParams } from "react-router-dom";
{path:'article/:id',element:<Article/>},//需要在router中這樣定義
const navigate = useNavigate();
navigate('/article/100');//傳參方式
const {id} = useParams();//取參方式
<h2>文章id:{id}</h2>
4.入口文件使用router
import {RouterProvider} from 'react-router-dom'
import router from './router/index'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<RouterProvider router={router} />
);
案例呈現(xiàn)

項(xiàng)目結(jié)構(gòu)

router/index.js 代碼
import { createBrowserRouter,Navigate} from 'react-router-dom'
import {AuthRoute} from './guard' //導(dǎo)航守衛(wèi)
import {lazy} from 'react'
// lazy 的作用:讓路由組件 按需加載,不浪費(fèi)流量,提升首屏速度,在入口文件搭配搭配 Suspense使用
const Login = lazy(()=>import('../components/login'))
const Layout = lazy(()=>import('../components/layout/index'))
const Home = lazy(()=>import('../components/layout/home'))
const About = lazy(()=>import('../components/layout/about'))
const Article = lazy(()=>import('../components/layout/article'))
const User = lazy(()=>import('../components/layout/user'))
const Notfound = lazy(()=>import('../components/404'))
const router = createBrowserRouter([
{
path:'/login',
element:<Login/>,
},
/* {
path:'/manage',
element:(
<AuthRoute>
<Layout/> //將所有需要登錄才能訪問的后臺(tái)頁面使用AuthRoute包裹
</AuthRoute>
),
},*/
{
path:'/',
element:(
<AuthRoute>
<Layout/> //將所有需要登錄才能訪問的后臺(tái)頁面使用AuthRoute包裹
</AuthRoute>
),
children:[
//index:true 類似vue的redirect
{index:true,element:<Navigate to="/home" replace/>},
{path:'home',element:<Home/>},
{path:'about',element:<About/>},
{path:'user',element:<User/>},
{path:'article/:id/:name',element:<Article/>},
],
},
{
path:'*',
element:<Notfound/>
}
])
export default router;
router/guard.js代碼 -- 導(dǎo)航守衛(wèi)
//導(dǎo)航守衛(wèi)
import { Navigate } from "react-router-dom";
export const AuthRoute = ({children})=>{
const isLoin = localStorage.getItem('token');
if(!isLoin){
return <Navigate to="/login" replace></Navigate>
}
return children;
}
index.js/main.js入口文件 代碼
import React ,{Suspense} from 'react';
import ReactDOM from 'react-dom/client';
import {RouterProvider} from 'react-router-dom'
import router from './router/index'
import './index.css'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Suspense fallback={<div>頁面加載中...</div>}>
<RouterProvider router={router} />
</Suspense>
</React.StrictMode>
);
login.js 登錄
import { useNavigate } from "react-router-dom";
const Login = ()=>{
const navigate = useNavigate();
const handleLogin = ()=>{
localStorage.setItem('token','123');
navigate('/home');
}
return (
<div>
<h1>login.vue</h1>
<button onClick={()=>{handleLogin()}}>登錄</button>
</div>
)
}
export default Login;
layout.js
import { useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
const Layout = ()=>{
const navigate = useNavigate();
const location = useLocation();
const [userid,setUserId] = useState('');
const navs = [
{path:'/home',label:'home'},
{path:'/about',label:'about'},
{path:'/user?id=100&name=jack',label:'user'},
];
const go = (path)=>{
navigate(path);
}
const logout = ()=>{
localStorage.removeItem('token');
go('/login')
}
return (
<div className="layout">
<div className="header">
{navs.map(e=><div onClick={()=>{go(e.path)}} key={e.path}>{e.label}</div>)}
<div onClick={()=>{logout()}}>退出登錄</div>
</div>
<div>
<input placeholder="輸入文章id" value={userid} onChange={(e)=>{setUserId(e.target.value)}}/>
<button onClick={()=>{go(`/article/${userid}/一篇文章`)}}>查看文章</button>
</div>
<div className="content">
<p>當(dāng)前路徑:{location.pathname}</p>
<hr/>
<div><Outlet/></div>//Outlet類似router-view
</div>
</div>
)
}
export default Layout;
user.js 代碼 : 通過useSearchParams 取參
import { useSearchParams } from "react-router-dom";
const Component = ()=>{
const [params] = useSearchParams();
const userId = params.get('id');
const userName = params.get('name');
return (
<div>
<div>用戶id:{userId}</div>
<div>用戶名:{userName}</div>
</div>
)
}
export default Component;
article.js : 通過 useParams的取參
import { useParams } from "react-router-dom";
const Article = ()=>{
const {id,name} = useParams();
return (
<div>
<div>文章id:{id}</div>
<div>文章標(biāo)題:{name}</div>
</div>
)
}
export default Article;