React 是一个用于构建用户界面的库。React 不是一个框架——它的应用甚至不局限于 Web 开发,它可以与其他库一起使用以渲染到特定环境。例如,React Native 可用于构建移动应用程序;React 360 可用于构建虚拟现实应用程序…
React
1.搭建开发环境
create-react-app快速搭建开发环境
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需要解析工具做解析才能渲染
function App() {
return (
<div className="App">
this is a app
</div>
);
}
识别js表达式
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值
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.条件渲染
逻辑运算符,三元表达式
//简单情况的条件渲染
{isLogin && <span>this is a sapn</span>}
{isLogin ? <span>this is a span</span> : <div>this is a div</div>}
//复杂情况的条件渲染
使用函数判断并返会jsx
5.事件绑定
on+事件名称 = {事件处理程序}
function App() {
const func=()=>{
console.log("test")
}
//事件参数e
const func=(e)=>{
console.log("test",e)
}
return (
<div className="App">
<div onClick={func}>click me</div>
</div>
);
}
传递参数 使用箭头函数调用
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
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视图,渲染组件只需要把组件当成标签书写即可
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 进行响应式
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
设置对象
const func = ()=>{
setCount({
...user,
name:tom
});
}
设置数组
setTodos([{
id: todos.length,
text: text
}, ...todos]);
8.小案例
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;
动态绑定类名
<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状态
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绑定
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对象
const test = () => {
console.log(inputRef.current)
}
3.动态获取input的value
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.实现重新聚焦
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))
/* console.log(value) */
}
}
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.父传子
组件之间的数据传递,父传子实现
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可传递任意的数据
- 数字,字符串,布尔值,数组,对象,函数,JSX
props是只读对象,子组件不能修改
2.children属性
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.子传父
父组件给子组件传递函数,子组件调用并传递参数
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.通过父组件传递给兄弟组件
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钩子函数获取消费数据
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开启了定时器,在下载时清理
清除副作用函数,在组件卸载时会自动执行
useEffect(()=>{
const timer = setInterval(()=>{
console.log("test")
},1000)
return ()=>{
//清除副作用
clearInterval(timer)
}
},[])
13.自定义hook
use打头的函数,可以实现逻辑的封装和复用
1.声明一个use打头的函数
2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)
使用解构赋值获得use打头的函数返回的对象的值
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
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封装请求
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
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获取最新的状态数据更新到视图中
<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>
//定义一个reducer函数
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
}
//使用reducer函数生成store实例
const store = Redux.createStore(reducer)
//通过store实例的subscribe订阅数据变化
store.subscribe(()=>{
console.log("state+|-")
document.querySelector(".text").innerText = store.getState().count
})
//通过dispatch函数提交状态
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
#counterStore.js
import {createSlice} from '@reduxjs/toolkit'
const counterStore = createSlice({
name:'counter',
//初始化state
initialState:{
count:0
},
reducers:{
increment(state){
state.count++
},
decrement(state){
state.count--
}
}
})
//解构
const {increment,decrement} = counterStore.actions
//获取reducer
const reducer = counterStore.reducer
//导出需要的模块
export {increment,decrement}
export default reducer
#index.js
import {configureStore} from '@reduxjs/toolkit';
//导入子模块
import counterReducer from './modules/counterStore'
const store = configureStore({
reducer:{
counter:counterReducer
}
})
export default store
#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>
);
#App.js
import {useSelector} from 'react-redux'
function App() {
//结构使用count
const {count} = useSelector(state=>state.counter)
return (
<div>
<div>{count}</div>
</div>
);
}
export default App;
修改store的数据,使用hook函数 useDispatch,它的作用是生产提交action对象的dispatch函数
#App.js
import {useDispatch, useSelector} from 'react-redux'
//导入actionCreater
import {increment,decrement} from './store/modules/counterStore'
function App() {
//结构使用count
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属性上
addToNumber(state,action){
state.count = action.payload
}
5.Redux与React异步修改
异步修改
1.创建store的写法不变,配置好同步修改方法
2.单独封装一个函数,在函数内部return一个新函数,在新函数中
2.1封装异步请求获取数据
2.2调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
3.组件中dispatch的写法保持不变
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
import {useEffect} from 'react'
import {useDispatch, useSelector} from 'react-redux'
//导入actionCreater
import {increment,decrement,addToNumber} from './store/modules/counterStore'
import {fetChannerllist} from './store/modules/channelStore'
function App() {
//结构使用count
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
const routes = {
{
path:"/about",
componet:about
}
}
1.创建项目并安装依赖
npx create-react-app react-router-pro
npm i
npm i react-router-dom
npm run start
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文件夹新建文件
const Article = ()=>{
return <div>Article</div>
}
export default Article
router文件夹定义router
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
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标签
import {Link} from 'react-router-dom'
const Login = ()=>{
return (<div>
Login
<Link to="/article">跳转到文章</Link>
</div>)
}
export default Login
2.编程式导航
使用useNavigate钩子
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.接收
const [params] = useSearchParams()
const id = params.get('id')
const name = params.get('name')
use
1.占位
{
path:'/article/:id',
element:<Article></Article>
}
2.传递
<Link to=”/article/1000”>跳转到文章</Link>
3.接收
const params = useParams()
const id = params.id
5.嵌套路由配置
使用children属性配置路由关系
使用Outlet组件配置二级路由渲染配置
设置默认路由
去掉path,改为index:true
6.404路由配置
当浏览器输入的url的路径在整个路由配置都找不到
{
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
const path = require('path')
module.exports = {
webpack:{
alias:{
'@':path.resolve(__dirname,'src')
}
}
}
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
jsconfig.json ===>vscode
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*":[
"src/*"
]
}
}
}
3.静态界面的布置
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
body{
div{
color: blue;
}
}
5.安装antd
npm i antd –save
6.增加表单校验规则
<Form.Item name="verifycode" rules={[{
required:true,
message:"请输入验证码"
}]}>
7.封装axios模块
//axios的封装处理
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
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相关的方法,存删取
function setTokens(token){
localStorage.setItem("token",token)
}
function getToken(){
return localStorage.getItem("token")
}
function removeToken(){
localStorage.removeItem("token")
}
export {
setTokens,
getToken,
removeToken
}
10.路由权限控制
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}
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
import * as echarts from 'echarts';
import { useEffect, useRef } from 'react';
const BarChart = ({title})=>{
const chartRef = useRef(null)
useEffect(()=>{
//获取渲染图标的dom节点
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
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.使用表单回填数据
const [form] = Form.useForm()
//获取传来的id参数
const [searchparam] = useSearchParams()
const [form] = Form.useForm()
//获取id
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 }}
// 注意:此处需要为富文本编辑表示的 content 文章内容设置默认值
initialValues={{ type: 1 , content: '' }}
onFinish={onFinish}
form={form}
>
15.打包运行
npm run build
本地运行
npm i -g serve serve -s build
16.路由懒加载
1.lazy函数进行动态导入
2.使用react的Suspense组件包裹路由组件
import {lazy} from 'react'
const Home = lazy(()=>import("@/pages/Home"))
const Article = lazy(()=>import("@/pages/Article"))
const Publish = lazy(()=>import("@/pages/Publish"))
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
"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
const path = require('path')
module.exports = {
webpack:{
alias:{
'@':path.resolve(__dirname,'src')
},
configure: (webpackConfig) => {
// webpackConfig自动注入的webpack配置对象
// 可以在这个函数中对它进行详细的自定义配置
// 只要最后return出去就行
let cdn = {
js: [],
css: []
}
// 只有生产环境才配置
whenProd(() => {
// key:需要不参与打包的具体的包
// value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下
// 通过import 导入的 react / react-dom
webpackConfig.externals = {
react: 'React',
'react-dom': 'ReactDOM'
}
// 配置现成的cdn 资源数组 现在是公共为了测试
// 实际开发的时候 用公司自己花钱买的cdn服务器
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: []
}
})
// 都是为了将来配置 htmlWebpackPlugin插件 将来在public/index.html注入
// cdn资源数组时 准备好的一些现成的资源
const { isFound, match } = getPlugin(
webpackConfig,
pluginByName('HtmlWebpackPlugin')
)
if (isFound) {
// 找到了HtmlWebpackPlugin的插件
match.userOptions.cdn = cdn
}
return webpackConfig
}
}
}
index.html
<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对象
const [state,dispatch] = useReducer(reducer,0)
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发生变化才执行函数,减少重复的不必要的计算
缓存的是计算的结果
使用:消耗非常大的计算
const [count1,setCount1] = useState(5)
useMemo(()=>{
return fib(count1)
},[count1])
21.React.memo
经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染
默认渲染机制,父组件渲染子组件跟着一起渲染
const memoComponent = memo(function son(){
return <div>son</div>
})
props 传递一个普通类型 prop改变时组件重新渲染
props 传递一个引用类型 prop改变的时候组件比较引用是否相等,再重新渲染
保证引用稳定 –》使用useMemo 组件渲染的过程中缓存一个值
22.useCallback
在组件多次重新渲染时缓存函数
const handler = useCallback(()=>{
console.log("123")
},[])
23.React.forwardRef
ref暴露DOM节点给父组件
const Son = forwardRef((prop,ref)=>{
return <input type="text" ref={ref}/>
})
function App(){
const SonRef = useRef(null)
const showREf = ()=>{
//console.log(SonRef)
SonRef.current.focus()
}
return (
<div className="App">
<Son ref={SonRef}/>
<button onClick={showREf}>focus</button>
</div>
)
}
export default App
24.useImperativeHandle
向父组件暴露子组件的方法
import { useRef,forwardRef, useImperativeHandle } from "react"
/* function Son(){
return <input type="text"/>
} */
/* 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 */
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模版
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.类组件中父子类传递信息
import {Component} from 'react'
class Son extends Component{
state={
msg : "son"
}
render(){
//使用属性 使用this.props.属性名
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消费数据和方法
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异步支持
//异步函数逻辑
fetchList:async ()=>{
const res = await fetch("http://geek.itheima.net/v1_0/channels")
const jsonRes = await res.json()
set({channelList:jsonRes.data.channels})
}
使用
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比较大时,可以采用切片模式进行拆分
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)=>{//...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自动推导
const [value,toggle] = useState(false)
const change = ()=>{
toggle(100)
}
//类型“100”的参数不能赋给类型“SetStateAction<boolean>”的参数
3.useState泛型参数
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
const [user,Setuser] = useState<Person | null>(null)
5.props类型
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类型来注解
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添加类型
import { useState } from "react"
/* type Person{
name:String,
age:Number
} */
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
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
当成稳定引用的存储器使用
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做智能提示
//vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve:{
alias:{
'@':path.resolve(__dirname,'./src')
}
}
})
npm i @types/node -D
vscode智能提示
//tsconfig.app.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*":[
"src/*"
]
},
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
API模块封装
通用泛型
type ResType<T> = {
message:String,
data:T
}
具体泛型
type User = {
id:number,
name:String
}
type UserList = {
users:User[]
}
使用泛型
function fetchUserList(){
return http.request<ResType<UserList>>({
url:"/channels"
})
}
反文件
export type UserList = {
users:User[]
}
export type ResType<T> = {
message:String,
data:T
}
type User = {
id:number,
name:String
}
import {http} from '@/utils'
import type {ResType,UserList} from '@/apis/type/user'
function fetchUserList(){
return http.request<ResType<UserList>>({
url:"/channels"
})
}
export {fetchUserList}
使用hook优化代码,将代码逻辑抽离
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}