这篇文章上次修改于 215 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
React 是一个用于构建用户界面的库。React 不是一个框架——它的应用甚至不局限于 Web 开发,它可以与其他库一起使用以渲染到特定环境。例如,React Native 可用于构建移动应用程序;React 360 可用于构建虚拟现实应用程序…
React
1.搭建开发环境
create-react-app快速搭建开发环境
1
| npx create-react-app react-basic(项目名)
|
node_modules:存放项目所依赖的一些第三方包文件
public:静态资源文件夹
- favicon.ico:导航图标
- index.html:项目首页的html模版
- logo192.png&logo512.png:两个logo图片
- manifest.json:应用加壳配置文件,在手机上描述我们应用程序的json文件
- robots.txt:爬虫协议文件
src:源码文件夹
- App.css:App组件的样式
- App.js:App组件
- App.test.js:自动化测试文件,用于给App做测试
- index.css:全局样式
- index.js:入口文件,所用组件都会通过index.js载入
- logo.svg:react的一个标志文件,sug文件是纯粹的XML,保证了在放大缩小时图像不失真
- reportWebVitals.js:导入了一个web-vitals的第三方库,用来检查应用程序的性能
- setupTests.js:从Jest-dom导入匹配器,在进行测试时使用
其它文件
.gitignore:git的一个文件,在这里可以配置不想提交到版本库里的文件
package-lock.json:项目安装包的版本号
package.json:存放react的一些依赖和指令
README.md:项目的介绍文件,运用markdown标记语言编写,在github等开源代码网站上,项目常用.md文件来进行介绍
2.JSX
JavaScript和XML缩写,表示JS代码中编写HTML模板结构
jsx需要解析工具做解析才能渲染
1 2 3 4 5 6 7
| function App() { return ( <div className="App"> this is a app </div> ); }
|

识别js表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let count = 10; let getname = ()=>{ return "Ha" } function App() { return ( <div className="App"> this is a app {/* 使用引号传递字符串 */} {'test'} {/* 识别变量 */} {count} {/* 函数调用 */} {getname()} {/* 方法调用 */} {new Date.now()} {/* 使用js对象 */} <div style={{color:'red'}}>this is a test</div> </div> ); }
|
3.动态渲染列表
map循环结构,加上唯一key值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let list = [ {id:'1001',name:'vue'}, {id:'1002',name:'react'}, {id:'1003',name:'angular'} ] function App() { return ( <div className="App"> <ul> /*{list.map(item => <li>{item.name}</li>)}*/ {list.map(item=><li key={item.id}>{item.name}</li>)} </ul> </div> ); }
export default App;
|
4.条件渲染
逻辑运算符,三元表达式
1 2 3 4 5
| {isLogin && <span>this is a sapn</span>} {isLogin ? <span>this is a span</span> : <div>this is a div</div>}
使用函数判断并返会jsx
|
5.事件绑定
on+事件名称 = {事件处理程序}
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function App() { const func=()=>{ console.log("test") } const func=(e)=>{ console.log("test",e) } return ( <div className="App"> <div onClick={func}>click me</div> </div> ); }
|
传递参数 使用箭头函数调用
1 2 3 4 5 6 7 8 9 10 11 12
| const test ="Hello React" function App() { const func=(test)=>{ console.log(test) }
return ( <div className="App"> <button onClick={()=>func(test)}>click me</button> </div> ); }
|
既要传递自定义参数,又要事件对象e
1 2 3 4 5 6 7 8 9 10 11 12 13
| const test ="Hello React" function App() { const func=(test,e)=>{ console.log(test+"____"+e) }
return ( <div className="App"> <button onClick={(e)=>func(test,e)}>click me</button> </div> ); }
|
6.组件
组件就是首字母大写的函数,内部存放了组件的逻辑和ui视图,渲染组件只需要把组件当成标签书写即可
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Button(){ return <button>click me</button> } const button = ()=>{ return <button>click me</button> } function App() { return ( <div className="App"> <Button/> </div> ); }
|
可以使用自闭合标签或成对标签
7.useState
UseState是一个React Hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果
相当于Vue3的reactive和ref 进行响应式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import {useState} from 'react' function App() { const [count,setCount] = useState(0) const func = ()=>{ setCount(count+1); } return ( <div className="App"> <button onClick={()=>func()}>click me</button> </div> ); }
export default App;
|
状态不可变
使用setCount函数改变Count
设置对象
1 2 3 4 5 6 7
| const func = ()=>{ setCount({ ...user, name:tom }); }
|
设置数组
1 2 3 4
| setTodos([{ id: todos.length, text: text }, ...todos]);
|
8.小案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import {useState} from 'react' let list = [ {id:'1001',name:'vue',author:"richu"}, {id:'1002',name:'react',author:"lisi"}, {id:'1003',name:'angular',author:"zhangsan"} ] const user = { uid:1001, avatar:"https://www.???.com", uname:"richu" }
function App() { const [commentslist,SetCommentslist] = useState(list) const deleteComments = (author)=>{ if (author === user.uname){ SetCommentslist(list.filter( item=> item.author !== author)) } } return ( <div className="App"> <div>{user.uname}</div> <input placeholder='发表评论'></input> <div>{commentslist.map(item=><div key={item.id}> <span>{item.name}</span> <button onClick={()=>deleteComments(item.author)}>删除</button> </div>)}</div> </div> ); } export default App;
|
动态绑定类名
1 2 3
| <div className={`${name=user.name && 'active'}`}></div>
<div className={className('item',{active:type === item.type })}></div>
|
active为类名 为true类名显示
9.受控表单绑定
使用React组件的状态(useState)控制表单的状态,双向绑定
通过value属性绑定react事件
绑定onChange事件,通过事件参数e拿到输入框最新的值 反向修改到react状态
1 2 3 4 5 6 7 8 9 10 11
| import {useState} from 'react'
function App() { const [value,SetValue] = useState('') return ( <div className="App"> <input value={value} onChange={(e)=>SetValue(e.target.value)} type='text'></input> </div> ); } export default App;
|
10.React获取Dom
使用useRef钩子函数
1.使用useRef创建ref对象,并与JSX绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import {useState,useRef} from 'react'
function App() { const [commentslist,SetCommentslist] = useState(list) const [value,SetValue] = useState('') const inputRef = useRef(null) const deleteComments = (author)=>{ if (author === user.uname){ SetCommentslist(list.filter( item=> item.author !== author)) console.log(value) } } const test = () => { console.log(inputRef.current) } return ( <div className="App"> <div>{user.uname}</div> <input placeholder='发表评论'></input> <div>{commentslist.map(item=><div key={item.id}> <span>{item.name}</span> <button onClick={()=>deleteComments(item.author)}>删除</button> </div>)}</div> <input value={value} onChange={(e)=>SetValue(e.target.value)} type='text'></input> <input ref={inputRef}></input> <button onClick={()=>test()}>click me!</button> </div> ); } export default App;
|
2.在DOM可用时,通过inputRef.current拿到DOM对象
1 2 3
| const test = () => { console.log(inputRef.current) }
|
3.动态获取input的value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import {useState,useRef} from 'react' let list = [ {id:'1001',name:'vue',author:"richu"}, {id:'1002',name:'react',author:"lisi"}, {id:'1003',name:'angular',author:"zhangsan"} ] const user = { uid:1001, avatar:"https://www.???.com", uname:"richu" }
function App() { const [commentslist,SetCommentslist] = useState(list) const [value,SetValue] = useState('') const inputRef = useRef(null) const [a,SetA] = useState(4); const deleteComments = (author)=>{ if (author === user.uname){ SetCommentslist(list.filter( item=> item.author !== author)) } } const test = () => { SetCommentslist([ ...commentslist ,{ id:'100'+a.toString(), name:value, author:user.uname }]) SetA(a+1) SetValue('') } return ( <div className="App"> <div>{user.uname}</div> <input value={value} placeholder='你怎么看' onChange={(e)=>SetValue(e.target.value)}></input> <button onClick={()=>test()}>发布</button> <div>{commentslist.map(item=><div key={item.id}> <span>{item.name}</span> <button onClick={()=>deleteComments(item.author)}>删除</button> </div>)}</div> </div> ); } export default App;
|
4.实现重新聚焦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import {useState,useRef} from 'react' let list = [ {id:'1001',name:'vue',author:"richu"}, {id:'1002',name:'react',author:"lisi"}, {id:'1003',name:'angular',author:"zhangsan"} ] const user = { uid:1001, avatar:"https://www.???.com", uname:"richu" }
function App() { const [commentslist,SetCommentslist] = useState(list) const [value,SetValue] = useState('') const inputRef = useRef(null) const [a,SetA] = useState(4); const deleteComments = (author)=>{ if (author === user.uname){ SetCommentslist(list.filter( item=> item.author !== author)) } } const test = () => { SetCommentslist([ ...commentslist ,{ id:'100'+a.toString(), name:value, author:user.uname }]) SetA(a+1) SetValue('') inputRef.current.focus() } return ( <div className="App"> <div>{user.uname}</div> <input ref={inputRef} value={value} placeholder='你怎么看' onChange={(e)=>SetValue(e.target.value)}></input> <button onClick={()=>test()}>发布</button> <div>{commentslist.map(item=><div key={item.id}> <span>{item.name}</span> <button onClick={()=>deleteComments(item.author)}>删除</button> </div>)}</div> </div> ); } export default App;
|
11.组件通信
1.父传子
组件之间的数据传递,父传子实现
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Son(props){ console.log(props) return <div>this is son</div> }
function App(){ const name = "hahah" return <div> <Son name={name}/> </div> }
export default App;
|
props可传递任意的数据
props是只读对象,子组件不能修改
2.children属性
1 2 3 4 5 6 7 8 9 10 11 12
| function Son(props){ console.log(props) return <div>this is son</div> }
function App(){ return <div> <Son><span>this is a span</span></Son> </div> }
export default App;
|
3.子传父
父组件给子组件传递函数,子组件调用并传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Son({GetMSG}){ const test = "test" return <button onClick={()=>GetMSG(test)}>click me !!!!!</button> }
function App(){ const GetMSG = (msg)=>{ console.log(msg) } return <div> <Son GetMSG={GetMSG}></Son> </div> }
export default App;
|
4.利用状态提升兄弟组件传递
1.通过子传父传递给共同的父组件
2.通过父组件传递给兄弟组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import {useState} from 'react'
function Son({GetMSG}){ const test = "test1" return <button onClick={()=>GetMSG(test)}>click me !!!!!</button> }
function Son1({name}){ return <div>this is Son1,{name}</div> }
function App(){ const [name,SetName] = useState("") const GetMSG = (msg)=>{ SetName(msg) } return <div> <Son GetMSG={GetMSG}></Son> <Son1 name={name}></Son1> </div> }
export default App;
|
5.使用context机制实现跨层级组件通信
使用createContext方法创建一个上下文对象
在顶层组件App中通过ctx.provider提供数据
在底层组件使用useContext钩子函数获取消费数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import {createContext, useContext} from 'react'
const MsgContext = createContext()
function Son(){ return <div><Son1/></div> }
function Son1(){ const test = useContext(MsgContext) return <div>this is Son1,{test}</div> }
function App(){ const msg = "test" return <div> <MsgContext.Provider value={msg}> <Son></Son> </MsgContext.Provider> </div> }
export default App;
|
12.useEffect
useEffect(()=>{},[])
参数已是函数,在函数内放置要执行的操作
参数2是数组,可选参,在数组里面放依赖项,不同依赖项会影响第一个参数函数的执行,当数组为空时,副作用函数在组件渲染完毕后执行一次
依赖项不同会有不同的执行表现
依赖项 |
副作用函数执行时机 |
没有依赖项 |
组件初始渲染,组件更新时执行 |
空数组依赖 |
只在初始渲染时执行 |
添加特定依赖 |
组件初始渲染,特性依赖项变化时执行 |
由渲染本身引起的对接组件外部的操作,副作用操作。比如在useEffect开启了定时器,在下载时清理
清除副作用函数,在组件卸载时会自动执行
1 2 3 4 5 6 7 8 9
| useEffect(()=>{ const timer = setInterval(()=>{ console.log("test") },1000) return ()=>{ clearInterval(timer) } },[])
|
13.自定义hook
use打头的函数,可以实现逻辑的封装和复用
1.声明一个use打头的函数
2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)
使用解构赋值获得use打头的函数返回的对象的值
1 2 3 4 5 6 7 8 9 10 11 12 13
| function gettoggle(){ const a= 10; const b=10; return { a, b } }
function App(){ const {a,b} = gettoggle() return <div></div> }
|
15.ReactHooks
只能在组件中或者其他hook函数(自定义hook函数)中调用
只能在组件的顶层调用,不能嵌套在if for 等其他函数中
使用useEffect优化案例
npm i json-server
npm i axios
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import {useState,useRef, useEffect} from 'react' import axios from 'axios'; const user = { uid:1001, avatar:"https://www.???.com", uname:"richu" }
function App() { const [commentslist,SetCommentslist] = useState([]) useEffect(()=>{ async function getlist(){ const res = await axios.get("http://localhost:3005/list") SetCommentslist(res.data) } getlist() },[]) const [value,SetValue] = useState('') const inputRef = useRef(null) const [a,SetA] = useState(4); const deleteComments = (author)=>{ if (author === user.uname){ SetCommentslist(commentslist.filter( item=> item.author !== author)) } } const test = () => { SetCommentslist([ ...commentslist ,{ id:'100'+a.toString(), name:value, author:user.uname }]) SetA(a+1) SetValue('') inputRef.current.focus() } return ( <div className="App"> <div>{user.uname}</div> <input ref={inputRef} value={value} placeholder='你怎么看' onChange={(e)=>SetValue(e.target.value)}></input> <button onClick={()=>test()}>发布</button> <div>{commentslist.map(item=><div key={item.id}> <span>{item.name}</span> <button onClick={()=>deleteComments(item.author)}>删除</button> </div>)}</div> </div> ); } export default App;
|
使用自定义hook封装请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function useGetlist(){ const [commentslist,SetCommentslist] = useState([]) useEffect(()=>{ async function getlist(){ const res = await axios.get("http://localhost:3005/list") SetCommentslist(res.data) } getlist() },[]) return { commentslist, SetCommentslist } }
|
封装Item
1 2 3 4 5 6 7 8
| function Item({item,deleteComments}){ return ( <div key={item.id}> <span>{item.name}</span> <button onClick={()=>deleteComments(item.author)}>删除</button> </div> ) }
|
16.Redux
1.Redux
类似pinia(vuex)
通过集中管理的方式管理应用的状态
定义一个reducer对象
使用createStore方法传入reducer函数 生成一个store实例对象
使用subscribe方法订阅数据的变化
dispatch方法提交action对象触发数据变化
getState获取最新的状态数据更新到视图中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <button class="mius">-</button> <span class="text">0</span> <button class="add">+</button> <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script> <script> function reducer (state = {count:0},action){ if (action.type === "INCREMENT"){ return {count:state.count+1} } if (action.type === "DECREMENT"){ return {count:state.count-1} } return state } const store = Redux.createStore(reducer)
store.subscribe(()=>{ console.log("state+|-") document.querySelector(".text").innerText = store.getState().count }) const mbt = document.querySelector(".mius"); mbt.addEventListener('click',()=>{ store.dispatch({ type:"DECREMENT" }) })
const abt = document.querySelector(".add"); mbt.addEventListener('click',()=>{ store.dispatch({ type:"INCREMENT" }) }) </script> </body> </html>
|
2.环境配置
redux Toolkit react-redux
1.使用CRA快速创建React项目
npx create-react-app test
2.安装配套工具
npm i @reduxjs/toolkit react-redux
3.启动项目
npm run start
通常集中状态管理的部分会创建一个store目录
子store模块放入modules中
store中的入口文件index.js管理组合modules的所有子模块,并导出store
3.使用redux
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #counterStore.js import {createSlice} from '@reduxjs/toolkit'
const counterStore = createSlice({ name:'counter', initialState:{ count:0 }, reducers:{ increment(state){ state.count++ }, decrement(state){ state.count-- } } })
const {increment,decrement} = counterStore.actions
const reducer = counterStore.reducer
export {increment,decrement} export default reducer
|
1 2 3 4 5 6 7 8 9 10 11 12
| #index.js import {configureStore} from '@reduxjs/toolkit';
import counterReducer from './modules/counterStore' const store = configureStore({ reducer:{ counter:counterReducer } })
export default store
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #index.js import ReactDOM from 'react-dom/client'; import App from './App'; import store from './store'; import { Provider } from 'react-redux'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App> </App> </Provider> );
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #App.js import {useSelector} from 'react-redux' function App() { const {count} = useSelector(state=>state.counter) return ( <div> <div>{count}</div> </div> ); }
export default App;
|
修改store的数据,使用hook函数 useDispatch,它的作用是生产提交action对象的dispatch函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #App.js import {useDispatch, useSelector} from 'react-redux'
import {increment,decrement} from './store/modules/counterStore' function App() { const {count} = useSelector(state=>state.counter) const dispatch = useDispatch() return ( <div> <button onClick={()=>dispatch(increment())}>+</button> <div>{count}</div> <button onClick={()=>dispatch(decrement())}>-</button> </div> ); }
export default App;
|
4.提交action时传递参数
在调用actionCreater的时候传递参数,参数会被传递到action对象的payload属性上
1 2 3
| addToNumber(state,action){ state.count = action.payload }
|
5.Redux与React异步修改
异步修改
1.创建store的写法不变,配置好同步修改方法
2.单独封装一个函数,在函数内部return一个新函数,在新函数中
2.1封装异步请求获取数据
2.2调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
3.组件中dispatch的写法保持不变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { createSlice} from '@reduxjs/toolkit' import axios from 'axios' const ChannelStore = createSlice({ name:'channel', initialState:{ channellist:[] }, reducers:{ setChannels(state,action){ state.channellist = action.payload } } }) const {setChannels} = ChannelStore.actions
const fetChannerllist = ()=>{ return async (dispatch)=>{ const res = await axios.get('http://geek.itheima.net/v1_0/channels') dispatch(setChannels(res.data.data.channels)) } }
export {fetChannerllist}
const reducer = ChannelStore.reducer
export default reducer
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import {useEffect} from 'react' import {useDispatch, useSelector} from 'react-redux'
import {increment,decrement,addToNumber} from './store/modules/counterStore' import {fetChannerllist} from './store/modules/channelStore' function App() { const {count} = useSelector(state=>state.counter) const {channellist} = useSelector(state=>state.channel) const dispatch = useDispatch() useEffect(()=>{ dispatch(fetChannerllist()) },[dispatch]) return ( <div> <button onClick={()=>dispatch(increment())}>+</button> <div>{count}</div> <button onClick={()=>dispatch(decrement())}>-</button> <button onClick={()=>dispatch(addToNumber(20))}>to 20</button> <ul> {channellist.map(item=><li key={item.id}>{item.name}</li>)} </ul> </div> ); }
export default App;
|
17.ReactRouter
路由,一个路径path对应一个Component
1 2 3 4 5 6
| const routes = { { path:"/about", componet:about } }
|
1.创建项目并安装依赖
npx create-react-app react-router-pro
npm i
npm i react-router-dom
npm run start
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals';
import {createBrowserRouter,RouterProvider} from 'react-router-dom'
const router = createBrowserRouter([ { path:'/login', element:<div>login</div> }, { path:'/article', element:<div>article</div> } ])
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <RouterProvider router={router}> <App /> </RouterProvider>
</React.StrictMode> );
reportWebVitals();
|
2.抽象路由模块
在page文件夹新建文件
1 2 3 4 5
| const Article = ()=>{ return <div>Article</div> }
export default Article
|
router文件夹定义router
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Article from '../page/Article' import Login from '../page/Login' import {createBrowserRouter} from 'react-router-dom'
const router = createBrowserRouter([ { path:'/login', element:<Login></Login> }, { path:'/article', element:<Article></Article> } ])
export default router
|
导入router
1 2 3 4 5 6 7 8 9 10 11
| import router from '../router'; import {RouterProvider} from 'react-router-dom'
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <RouterProvider router={router}> <App /> </RouterProvider> </React.StrictMode> );
|
3.路由导航跳转
1.声明式导航
使用Link标签
1 2 3 4 5 6 7 8 9 10
| import {Link} from 'react-router-dom'
const Login = ()=>{ return (<div> Login <Link to="/article">跳转到文章</Link> </div>) }
export default Login
|
2.编程式导航
使用useNavigate钩子
1 2 3 4 5 6 7 8 9 10 11
| import {useNavigate} from 'react-router-dom'
const Article = ()=>{ const navigate = useNavigate() return (<div> Article <button onClick={()=>navigate("/login")}>去到文章</button> </div>) }
export default Article
|
4.导航传参
useSearchParams
1.传递
onClick={()=>navigate(“/login?id=1000&name=tom”)}
2.接收
1 2 3
| const [params] = useSearchParams() const id = params.get('id') const name = params.get('name')
|
use
1.占位
1 2 3 4
| { path:'/article/:id', element:<Article></Article> }
|
2.传递
<Link to=”/article/1000”>跳转到文章</Link>
3.接收
1 2
| const params = useParams() const id = params.id
|
5.嵌套路由配置
使用children属性配置路由关系
使用Outlet组件配置二级路由渲染配置
设置默认路由
去掉path,改为index:true
6.404路由配置
当浏览器输入的url的路径在整个路由配置都找不到
1 2 3 4
| { path:'*', element:<NotFound></NotFound> }
|
history模式
createBrowerRouter
hash模式
createHashRouter
18.项目练习–>记账本
1.基本配置
1.Redux状态管理 @reduxjs/toolkit react-redux
2.路由 react-router-dom
3.时间 dayjs
4.class类名处理 classnames
5.移动端组件 antd-mobile
6.请求插件axios
npm i @reduxjs/toolkit react-redux react-router-dom dayjs classnames antd-mobile axios
2.别名路径配置
craco ===>webpack
- npm i @craco/craco
- craco.config.js
1 2 3 4 5 6 7 8 9
| const path = require('path')
module.exports = { webpack:{ alias:{ '@':path.resolve(__dirname,'src') } } }
|
1 2 3 4 5 6
| "scripts": { "start": "craco start", "build": "craco build", "test": "react-scripts test", "eject": "react-scripts eject" }
|
jsconfig.json ===>vscode
1 2 3 4 5 6 7 8 9 10
| { "compilerOptions": { "baseUrl": "./", "paths": { "@/*":[ "src/*" ] } } }
|
3.静态界面的布置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import { useEffect } from 'react' import { useDispatch } from 'react-redux' import {Outlet} from 'react-router-dom' import { getBillList } from '@/store/modules/billStore' import './demo.css' import {BillOutline, CalculatorOutline, AddCircleOutline } from 'antd-mobile-icons' import {TabBar } from 'antd-mobile' const tabs = [ { key: '/month', title: '月度账单', icon: <BillOutline />, }, { key: '/message', title: '记账', icon: <CalculatorOutline />, }, { key: '/year', title: '年度账单', icon: <AddCircleOutline />, }, ]
const Layout = ()=>{ const dispatch = useDispatch() useEffect(()=>{ dispatch(getBillList()) },[dispatch]) return <div className='layout'> <div className='container'> <Outlet/> </div> <div className="footer"> <TabBar /* onChange={value => setRouteActive(value)} */> {tabs.map(item => ( <TabBar.Item key={item.key} icon={item.icon} title={item.title} /> ))} </TabBar> </div> </div> }
export default Layout
|
4.安装SCSS
npm install sass -D
1 2 3 4 5
| body{ div{ color: blue; } }
|
5.安装antd
npm i antd –save
6.增加表单校验规则
1 2 3 4
| <Form.Item name="verifycode" rules={[{ required:true, message:"请输入验证码" }]}>
|
7.封装axios模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import axios from "axios";
const request = axios.create({ baseURL:"", timeout:5000 })
request.interceptors.request.use((config)=>{ return config },(error)=>{ return Promise.reject(error) })
request.interceptors.response.use((response)=>{ return response.data },(error)=>{ return Promise.reject(error) })
export {request}
|
8.redux封装token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import {createSlice} from '@reduxjs/toolkit' import {request} from '@/utils/index' const userStore = createSlice({ name:"user", initialState:{ token:'' }, reducers:{ setToken(state,action){ state.token = action.payload } } })
const {setToken} = userStore.actions const fetchLogin = (loginForm)=>{ return async (dispatch)=>{ const res = await request.post('/authorizations',loginForm) dispatch(setToken(res.data.token)) } } const userReducer = userStore.reducer
export {setToken,fetchLogin} export default userReducer
|
9.封装与token相关的方法,存删取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function setTokens(token){ localStorage.setItem("token",token) }
function getToken(){ return localStorage.getItem("token") }
function removeToken(){ localStorage.removeItem("token") }
export { setTokens, getToken, removeToken }
|
10.路由权限控制
1 2 3 4 5 6 7 8 9 10 11 12 13
| import {getToken} from '@/utils' import { Navigate } from 'react-router-dom'
function AuthRoute({children}){ const token = getToken() if (token){ return <>{children}</> }else{ return <Navigate to={'/login'} replace/> } }
export {AuthRoute}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Login from "@/pages/Login"; import Layout from "@/pages/Layout"; import {AuthRoute} from '@/components/AuthRoute' import {createBrowserRouter} from 'react-router-dom'
const router = createBrowserRouter([ { path:"/", element:<AuthRoute><Layout/></AuthRoute> }, { path:"/login", element:<Login/> } ])
export default router
|
11.normalize.css
npm i normalize.css
初始化样式
当token失效后访问后端接口会返回401状态码
12.echarts
npm install echarts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import * as echarts from 'echarts'; import { useEffect, useRef } from 'react';
const BarChart = ({title})=>{ const chartRef = useRef(null) useEffect(()=>{ const chartDom = chartRef.current const myChart = echarts.init(chartDom); const option = { title:{ text: title }, xAxis: { type: 'category', data: ['React','Angular','Vue'] }, yAxis: { type: 'value' }, series: [ { data: [150 , 120, 200], type: 'bar' } ] }; option && myChart.setOption(option); },[]) return <div ref={chartRef} style={{width:'500px',height:'500px'}}>Home</div> }
export default BarChart
|
13.统一管理API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { request } from "@/utils";
function loginAPI(formData){ return request({ url:'/authorizations', method:'POST', data:formData }) }
function getProfileAPT(){ return request({ url:'/user/profile', method:'GET' }) }
export {loginAPI,getProfileAPT}
|
14.富文本
npm i react-quill@2.0.0-beta.2 –legacy-peer-deps
15.使用表单回填数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const [form] = Form.useForm()
const [searchparam] = useSearchParams() const [form] = Form.useForm()
const myid = searchparam.get("id") useEffect(()=>{ async function getAriticle(){ const res = await getAriticleByIdAPI(myid) form.setFieldValue(res.data) } getAriticle() },[myid])
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 16 }} initialValues={{ type: 1 , content: '' }} onFinish={onFinish} form={form} >
|
15.打包运行
npm run build
本地运行
1 2
| npm i -g serve serve -s build
|
16.路由懒加载
1.lazy函数进行动态导入
2.使用react的Suspense组件包裹路由组件
1 2 3 4 5
| import {lazy} from 'react'
const Home = lazy(()=>import("@/pages/Home")) const Article = lazy(()=>import("@/pages/Article")) const Publish = lazy(()=>import("@/pages/Publish"))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| children:[ { index:true, element:<Suspense fallback={"加载中"}><Home></Home></Suspense> }, { path:"/publish", element: <Suspense fallback={"加载中"}><Publish></Publish></Suspense> }, { path:"/article", element:<Suspense fallback={"加载中"}><Article></Article></Suspense> } ]
|
17.包体积分析
npm i source-map-explorer
1 2 3 4 5 6 7
| "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject", "analyze":"source-map-explorer build/static/js/*.js" },
|
18.CDN配置
craco.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| const path = require('path')
module.exports = { webpack:{ alias:{ '@':path.resolve(__dirname,'src') }, configure: (webpackConfig) => { let cdn = { js: [], css: [] } whenProd(() => { webpackConfig.externals = { react: 'React', 'react-dom': 'ReactDOM' } cdn = { js: [ 'https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js', ], css: [] } }) const { isFound, match } = getPlugin( webpackConfig, pluginByName('HtmlWebpackPlugin') ) if (isFound) { match.userOptions.cdn = cdn } return webpackConfig } } }
|
index.html
1 2 3 4 5 6 7
| <body> <div id="root"></div> <!-- 加载第三发包的 CDN 链接 --> <% htmlWebpackPlugin.options.cdn.js.forEach(cdnURL => { %> <script src="<%= cdnURL %>"></script> <% }) %> </body>
|
19.useReducer
和useState类似,用来管理相对复杂的状态数据
1.定义一个reducer函数(根据不同的action返回不同的新状态)
2.在组件中调用useReducer,并传入reducer函数和状态的初始值
3.状态发生时通过dispatch分派一个action对象
1
| const [state,dispatch] = useReducer(reducer,0)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import {useReducer} from 'react'
function reducer(state,action){ switch(action.type){ case "INC": return state+1 case "DEC": return state-1 default: return state } }
function App() { const [state,dispatch] = useReducer(reducer,0) return ( <div className="App"> this is app ---{state} <button onClick={()=>dispatch({type:"INC"})}>++</button> <button onClick={()=>dispatch({type:"DEC"})}>--</button> </div> ); }
export default App;
|
20useMemo
只有在Count1发生变化才执行函数,减少重复的不必要的计算
缓存的是计算的结果
使用:消耗非常大的计算
1 2 3 4
| const [count1,setCount1] = useState(5) useMemo(()=>{ return fib(count1) },[count1])
|
21.React.memo
经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染
默认渲染机制,父组件渲染子组件跟着一起渲染
1 2 3
| const memoComponent = memo(function son(){ return <div>son</div> })
|
props 传递一个普通类型 prop改变时组件重新渲染
props 传递一个引用类型 prop改变的时候组件比较引用是否相等,再重新渲染
保证引用稳定 –》使用useMemo 组件渲染的过程中缓存一个值
22.useCallback
在组件多次重新渲染时缓存函数
1 2 3
| const handler = useCallback(()=>{ console.log("123") },[])
|
23.React.forwardRef
ref暴露DOM节点给父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const Son = forwardRef((prop,ref)=>{ return <input type="text" ref={ref}/> })
function App(){ const SonRef = useRef(null) const showREf = ()=>{ SonRef.current.focus() } return ( <div className="App"> <Son ref={SonRef}/> <button onClick={showREf}>focus</button> </div> ) }
export default App
|
24.useImperativeHandle
向父组件暴露子组件的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import { useRef,forwardRef, useImperativeHandle } from "react"
const Son = forwardRef((prop,ref)=>{ const inputRef = useRef(null) const inputfocus = ()=>{ inputRef.current.focus() } useImperativeHandle(ref,()=>{ return { inputfocus } }) return <input type="text" ref={inputRef}/> })
function App(){ const SonRef = useRef(null) const handlerclick = ()=>{ SonRef.current.inputfocus() } return ( <div className="App"> <Son ref={SonRef}/> <button onClick={handlerclick}>focus</button> </div> ) }
export default App
|
25类组件API
1.定义类属性state定义状态数据
2.通过setState方法来修改状态数据
3.通过reducer来写render来写UI模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { Component } from "react";
class Counter extends Component{ state = { count : 0 } setCount = ()=>{ this.setState({ count:this.state.count + 1 }) }
render(){ return <button onClick={this.setCount}>{this.state.count}</button> } }
function App () { return ( <> <Counter/> </> ) }
export default App
|
26.类组件的生命周期
componentDidMount:组件挂载完毕自动执行 异步数据获取
componentDidUpdate:组件更新
componentWillUnmount:组件卸载时自动执行 清理副作用
27.类组件中父子类传递信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import {Component} from 'react'
class Son extends Component{ state={ msg : "son" } render(){ return <div> son --{this.props.msg} <button onClick={this.props.onGet(this.state.msg)}>传递</button> </div> } }
class Parent extends Component{ state = { msg : "test" } onGet = (Sonmsg)=>{ console.log(Sonmsg) } render() { return (<div>father <Son msg={this.state.msg} onGet={this.onGet}/> </div> ) } }
function App(){ return ( <div> <Parent/> </div> ) }
export default App
|
27极简的状态管理工具zustand
1.zustand的基本使用
1.创建store 状态数据操作方法
2.component消费数据和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import {create} from 'zustand'
const useStore = create((set)=>{ return { count:1, inc:()=>{ set((state)=>({count:state.count + 1})) set({count:100}) } } })
function App(){ const {count,inc} = useStore() return ( <div> <button onClick={inc}>{count}</button> </div> ) }
export default App
|
2.zustand异步支持
1 2 3 4 5 6
| fetchList:async ()=>{ const res = await fetch("http://geek.itheima.net/v1_0/channels") const jsonRes = await res.json() set({channelList:jsonRes.data.channels}) }
|
使用
1 2 3 4 5 6 7 8 9 10 11 12
| function App(){ const {count,inc,fetchList,channelList} = useStore() useEffect(()=>{ fetchList() },[fetchList]) return ( <div> <button onClick={inc}>{count}</button> <ul>{channelList.map((item)=><li key={item.id}>{item.name}</li>)}</ul> </div> ) }
|
3.zustand切片模式
当单个store比较大时,可以采用切片模式进行拆分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { useEffect } from 'react' import {create} from 'zustand'
const countStore = (set)=>{ return { count:1, inc:()=>{ set((state)=>({count:state.count + 1})) }, } }
const channelStore = (set)=>{ return { channelList:[], fetchList: async ()=>{ const res = await fetch("http://geek.itheima.net/v1_0/channels") const jsonRes = await res.json() set({channelList:jsonRes.data.channels}) } } }
const useStore = create((...a)=>{ return { ...countStore(...a), ...channelStore(...a) } })
function App(){ const {count,inc,fetchList,channelList} = useStore() useEffect(()=>{ fetchList() },[fetchList]) return ( <div> <button onClick={inc}>{count}</button> <ul>{channelList.map((item)=><li key={item.id}>{item.name}</li>)}</ul> </div> ) }
export default App
|
28.React+TypeScript
1.配置安装
npm create vite@latest react-ts-pro – –template react-ts
npm i
2.useState自动推导
1 2 3 4 5
| const [value,toggle] = useState(false) const change = ()=>{ toggle(100) }
|
3.useState泛型参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| type Person{ name:String, age:Number }
function App() { const [user,Setuser] = useState<Person>() return ( <> <div> this is app </div> </> ) }
|
限制useState函数的参数为Person | ()=>Person
限制setuser函数的参数必须满足类型为Person | ()=>User | undefined
Person状态数据具备User类型相关的类型提示
4.useState初始值为null
限制useState的参数可以是Person | null
限制setUser的参数可以是Person | null
1
| const [user,Setuser] = useState<Person | null>(null)
|
5.props类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type Props={ className:String }
function Son(props:Props){ const {className} = props console.log(className) return (<div>test</div>) }
function App() { return ( <> <div> this is app <Son className={"test"}/> </div> </> ) }
|
6.children
children是特殊的props,支持不同的类型数据的传入,需要通过一个内置的ReactNode类型来注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| type Props={ className:String children:React.ReactNode }
function Son(props:Props){ const {className,children} = props console.log(className) return (<div>{children}</div>) }
function App() { return ( <> <div> this is app <Son className={"test"}>this is son</Son> </div> </> ) }
|
7.为事件prop添加类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import { useState } from "react"
type Props={ onGetMsg:(msg:String)=>void }
function Son(props:Props){ const {onGetMsg} = props const msg = "hello" return <button onClick={()=>onGetMsg(msg)}>click me!!!</button> }
function App() { const onGetMsg = (msg:String)=>{ console.log(msg) } return ( <> <div> this is app <Son onGetMsg={onGetMsg}/> </div> </> ) }
export default App
|
8.useRef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { useEffect, useRef, useState } from "react"
function App() { const domRef = useRef<HTMLInputElement>(null) useEffect(()=>{ domRef.current?.focus() },[domRef]) return ( <> <div> this is app <input ref={domRef} placeholder="input"></input> </div> </> ) }
export default App
|
当成稳定引用的存储器使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { useEffect, useRef, useState } from "react"
function App() { const domRef = useRef<HTMLInputElement>(null) const timeRef = useRef<number | undefined>(undefined) useEffect(()=>{ domRef.current?.focus() timeRef.current= setInterval(() => { console.log("1"); }, 1000);
return ()=>clearTimeout(timeRef.current) },[domRef]) return ( <> <div> this is app <input ref={domRef} placeholder="input"></input> </div> </> ) }
export default App
|
9.路径别名配置
让vite做路径解析
让VScode做智能提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path'
export default defineConfig({ plugins: [react()], resolve:{ alias:{ '@':path.resolve(__dirname,'./src') } } })
|
npm i @types/node -D
vscode智能提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "baseUrl": ".", "paths": { "@/*":[ "src/*" ] }, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx",
"strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src"] }
|
API模块封装
通用泛型
1 2 3 4
| type ResType<T> = { message:String, data:T }
|
具体泛型
1 2 3 4 5 6 7 8
| type User = { id:number, name:String }
type UserList = { users:User[] }
|
使用泛型
1 2 3 4 5
| function fetchUserList(){ return http.request<ResType<UserList>>({ url:"/channels" }) }
|
反文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| export type UserList = { users:User[] }
export type ResType<T> = { message:String, data:T }
type User = { id:number, name:String }
|
1 2 3 4 5 6 7 8 9 10 11
| import {http} from '@/utils' import type {ResType,UserList} from '@/apis/type/user'
function fetchUserList(){ return http.request<ResType<UserList>>({ url:"/channels" }) }
export {fetchUserList}
|
使用hook优化代码,将代码逻辑抽离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { useEffect, useState } from 'react'; import type {User} from '@/apis/type/user' import {fetchUserList} from '@/apis/user'
const useTab = ()=>{ const [userList,setUserList] = useState<User[]>([]) useEffect(()=>{ const getUsers = async ()=>{ try{ const res = await fetchUserList() setUserList(res.data.data.channels) }catch(error){ throw new Error("error") } } getUsers() },[]) return { userList } }
export {useTab}
|