关键点:
lazy懒加载
Suspense组件(添加加载提示)
utils文件夹
–LazyLoad.js
//lazy懒加载 Suspense 组件(添加加载提示)
import {lazy,Suspense} from 'react'
export default function LazyLoad(url){
//导入组件
const Elem=lazy(()=>import('../pages'+url))
//Suspense 提供一个加载提示
return
<Suspense fallback={<h2>loading...</h2>}>
<Elem></Elem>
</Suspense>
}
router文件夹–
baseRoutes.js
导入lazyLoad
import LazyLoad from "../utils/LazyLoad";
import Home from "../pages/Home";
import About from "../pages/About";
import Produce from "../pages/Produce";
// import OrderList from "../pages/admin/OrderList";
import Admin from "../pages/admin/Admin";
// import Dash from "../pages/admin/Dash"
import LazyLoad from "../utils/LazyLoad";
// 基本路由配置
const baseRoutes = [
{
path: "",
element: <Home></Home>,
},
{
path: "/about",
element: <About></About>,
},
{
path: "/produce/:id",
element: <Produce></Produce>,
},
{
path:"/admin/*",
element:<Admin></Admin>,
children:[
{path:'',element:LazyLoad('/admin/Dash.js')},
{path:'dash',element:LazyLoad('/admin/Dash.js')},
{path:'orderlist',element:LazyLoad('/admin/OrderList.js')},
]
}
];
export default baseRoutes
index.js
import { useEffect, useState } from "react";
// useRoutes 根据路由配置 创建路由
import { useRoutes } from "react-router-dom";
import baseRoutes from "./baseRoutes";
function RouterView() {
// 常见路由
const element = useRoutes(baseRoutes);
return <>{element}</>;
}
export default RouterView;
lazy Suspense
此时我们发现第一次点击的时候会出现路由懒加载,第二次就不再请求数据
npm i axios
import axios from 'axios'
const request=axios.create({
baseURL:"http://dida100.com:8888",
timeout:5000
})
request.interceptors.request.use(config=>{
if(sessionStorage.getItem("token")){
config.headers["Authorization"]="Bearer "+sessionStorage.setItem("token")
}
return config
})
export default request
import request from '../utils/request'
export async function loginServer(data){
//async 和 await 结合
const result=await request.post("/api/login",data)
//状态码为200 成功
if(result.status===200){
return result.data
}else{
//请求失败
//throw抛出 新的错误
throw new Error("网络请求失败")
}
}
(没有载荷是由于接口里面没写data) 这里面input实现双向绑定是通过onChange事件 onChange={e=>setUser({...user,password:e.target.value})}这里的...是扩展,就是面的name与password,用新的值去替换前面的password
import {loginServer} from '../server/user'
import {useState} from 'react'
import {useNavigate,useLocation} from 'react-router-dom'
import {parse} from '../utils'
function Home() {
const [user,setUser]=useState({name:"",password:""})
const navigate=useNavigate()
const location=useLocation()
var search=parse(location.search)
function login(){
loginServer(user)
.then(res=>{
if(res.code===200){
sessionStorage.setItem("token",res.token)
sessionStorage.setItem("user",JSON.stringify(res.user))
var redirect=search.redirect||'admin'
navigate(redirect)
}
})
}
return ( <div>
用户名:<input value={user.name} onChange={e=>setUser({...user,name:e.target.value})}/><br />
密码:<input value={user.password} onChange={e=>setUser({...user,password:e.target.value})}/><br />
<button onClick={login}>登录</button>
</div> );
}
export default Home;
通过新建一个组件实现的 pages页面新建一个private.js 如果有token就跳转到private的子组件props.children,如果没有就返回首页
import {Navigate,useLocation} from 'react-router-dom'
function Private(props) {
const location=useLocation()
console.log(location);
if(sessionStorage.getItem("token")){
return <>{props.children}</>
}else{
return <Navigate to={{pathname:'/',search:"?redirect="+location.pathname}} />;
}
}
export default Private;
在路由基础配置中的element中,用
import Private from "../pages/Private";
const baseRoutes = [
path:"/admin/*",
element:<Private><Admin></Admin></Private>,
]
Home.js
import {useNavigate,useLocation} from 'react-router-dom'
import {parse} from '../utils'
const location=useLocation()
//把location的search转换为对象
var search=parse(location.search)
如果search对象有redirext属性就用redirect,没有返回admin
var redirect=search.redirect||'admin'
utils里面 index.js
// 把?name=mumu&age=18
// 转换为{name:"mumu",age:18}
export function parse(url) {
var temp = url.slice(1).split("&");
var search = {};
for (var i = 0; i < temp.length; i++) {
var item = temp[i].split("=");
search[item[0]] = item[1];
}
return search;
}
单向数据流
组成
state状态数据
reducer处理状态的方法
store仓库
actions调用reducer的方法
安装
npm i redux react-redux redux-thunk redux-logger @reduxjs/toolkit -S
redux全局状态管理
react-redux 连接组件与redux
redux-thunk 处理异步actions
redux-logger 日志
redux-persist 本地存储
新建store
—index.js整个仓库
//整个仓库
// createStore 创建仓库,applyMiddleware处理中间件(日志,异步,本地存储)
// combineReducers合并多个处理counterReducer,userReducer,productionReducer
import {createStore,applyMiddleware,combineReducers} from 'redux'
//导入处理计数器
import counterReducer from './counterReducer'
//导入异步处理
import thunk from 'redux-thunk'
//导入日志
import logger from 'redux-logger'
//创建仓库
const store=createStore(
combineReducers({counter:counterReducer}),
applyMiddleware(thunk,logger)
)
//导出仓库
export default store
store有三个核心方法:
dispatch发起动作 触发reducer 返回新的state
subscribe 监听store中state的变化 执行回调函数
getState 获取stoTe中的数据
counterReducer.js 处理counter数据
import {INCREMENT} from './types'
//处理counter数据 定义初始化state
const initialState={
num:1
}
//定义一个处理器
function counterReducer(state=initialState,action){
//根据动作的类型
switch(action.type){
case INCREMENT:
//如果是加,返回原来的数据state,把num值加1
return {...state,num:state.num+1}
default:
return state
}
}
export default counterReducer
counterAction.js调用CounterReducer动作
type后面跟的是常量,需要两个页面一致,所以我们
store --type.js 存放reducer动作类型
export const INCREMENT=“INCREMENT”
在其他页面导入,引号就可以去掉了,不容易出错,很规范
import {INCREMENT} from './types'
store有三个核心方法(原生):
dispatch发起动作 触发reducer 返回新的state
subscribe 监听store中state的变化 执行回调函数
About.js
原生的三个都得写
这三个方法哪来的?
我们在index.js创建仓库的时候就带了这三个方法,通过createStore方法获取的
counter怎么来的?
const store=createStore( combineReducers({counter:counterReducer}), applyMiddleware(thunk,logger) )
dispatch(itype: “INCREMENT”})中的参数就是counterReducer的action根据type的值
//导入仓库 getState获取state
//dispatch发送事件执行 reducer改变state subscribe监听state数据的变化
import store from '../store/index.js'
//导入动作
import {add} from '../store/counterAction'
import {useEffect,useState} from 'react'
function About() {
//创建value的状态 响应式更新store中的num 初始赋值
const [value,setValue]=useState(store.getState().counter.num)
useEffect(()=>{
//监听store变化用store的num更新组件的value
store.subscribe(()=>{
setValue(store.getState().counter.num)
})
},[])
return (
<div>about
<p>value-store里面的num:{value}</p>
<button onClick={()=>store.dispatch(add())}>+</button>
</div>
);
}
export default About;
index.js
导入数据提供器Provider
import {Provider} from 'react-redux'
包装
使用
const num=useSelector(state=>state.counter.num)
const dispatch=useDispatch()
onClick={()=>dispatch({type:"INCREMENT"})}
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
//导入store
import store from './store'
//导入数据提供器
import {Provider} from 'react-redux'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// 渲染多次 去掉严格模式
//
<Provider store={store}>
<App />
</Provider>
//
);
OrderList使用选择器useSelector
这俩方式都可以实现
//使用选择器
import {useSelector,useDispatch} from 'react-redux'
//导入动作
import {add} from '../../store/counterAction'
function OrderList() {
//选择到state中的counter的num
const num=useSelector(state=>state.counter.num)
//创建一个触发器
const dispatch=useDispatch()
return (
<div>
orderlist
<p>
{/* 单击发送动作给reducer 处理返回新的state数据 */}
<button onClick={()=>dispatch({type:"INCREMENT"})}>{num}</button>
<button onClick={()=>dispatch(add())}>{num}</button>
</p>
</div>
);
}
export default OrderList;
他们之前是同步的,其他页面也会改变
produce.js
import {useParams} from 'react-router-dom'
//导入连接器 把state和actions方法转换props
import {connect} from 'react-redux'
//导入counterAction 里面的所有动作方法
import * as actions from '../store/counterAction'
console.log(actions);
function Produce(props) {
//获取产品参数
const params=useParams()
return (
<div>产品页面-{params.id}
{/* props.add()}>{props.counter.num}
*/}
<p onClick={()=>props.add()}>{props.num}</p>
</div>
);
}
//props.counter.num从connect第一个参数 状态
//props.add 从connect的第二个参数 方法
// export default Produce;
export default connect(state=>({num:state.counter.num}),{add:actions.add})(Produce)