Skip to main content

React

组件传参 js

父传子

import { Layout } from "antd";
import React from "react";

function FunctionComp(props) {
return <h1>函数式组件接收到的参数:{props.msg}</h1>;
}
class ClassComp extends React.Component {
render() {
return <h1>类组件接收到的参数:{this.props.msg}</h1>;
}
}
const App = () => {
return (
<>
<FunctionComp msg={"123"} />
<hr />
<ClassComp msg={"123"} />
</>
);
};

export default App;

image-20230115232632186

子传父

import React, { useState } from "react";

function FunctionComp(props) {
const { appfunc } = props;
return (
<button
onClick={() => {
appfunc(2);
}}
>
让父组件num变成2
</button>
);
}
class ClassComp extends React.Component {
render() {
return (
<button
onClick={() => {
this.props.appfunc(5);
}}
>
让父组件num变成5
</button>
);
}
}
const App = () => {
const [num, setNum] = useState(0);
const appfunc = (num) => {
setNum(num);
};
return (
<>
<h1>父组件中num的值:{num}</h1>
<hr />
<FunctionComp appfunc={appfunc} />
<hr />
<ClassComp appfunc={appfunc} />
</>
);
};

export default App;

image-20230115233254600

image-20230115233242399

兄弟间互传

组件传参 ts

方式一

子组件需要接收一个HeroLegend[]数组的参数,并且该参数可有可无

import axios from "axios";
import React, { useEffect } from "react";
import { HeroLegend } from "typings";

const Legend = ({ legend }: { legend?: HeroLegend[] | undefined }) => {
console.log(legend);

return <div>Legend</div>;
};
export default Legend;

父组件中使用

<Legend legend={legend.current} />

legend.current是HeroLegend[]类型的数据

方式二

import axios from "axios";
import React, { useEffect } from "react";
import { HeroLegend } from "typings";

const Legend = (props: { legend?: HeroLegend[] | undefined }) => {
console.log(props.legend);

return <div>Legend</div>;
};
export default Legend;

父组件中使用

<Legend legend={legend.current} />

legend.current是HeroLegend[]类型的数据

方式三

import axios from "axios";
import React, { useEffect } from "react";
import { HeroLegend } from "typings";
interface HeroLegendProps {
legend: HeroLegend[] | undefined;
}
const Legend: React.FC<HeroLegendProps> = (props) => {
console.log(props.legend);

return <div>Legend</div>;
};
export default Legend;

方式四

还是方式三的形式,不过是将参数解构出来

import axios from "axios";
import React, { useEffect } from "react";
import { HeroLegend } from "typings";
interface HeroLegendProps {
legend: HeroLegend[] | undefined;
}
const Legend: React.FC<HeroLegendProps> = ({ legend }) => {
console.log(legend);
return <div>Legend</div>;
};
export default Legend;

Hooks

useEffect

useEffect(() => {
document.title = `You clicked ${count} times`;
}, []);

第一个参数是函数,当页面渲染时会自动调用一次,第二个参数是一个空数组,如果第二个参数存在,那么第一个参数只会被执行一次

useSearchParams()

见路由参数

useParams()

见路由参数

useLocation()

见路由参数

useInRouterContext()

路由上下文,只要某个组件被 BrowserRouter 或者是 HashRouter 包裹,都会返回 true

useNavigationType()

  1. 作用:返回当前的导航类型(用户如何来到当前页面的)

  2. 返回值POPPUSHREPLACE

    备注:POP是指在浏览器中打开了这个路由组件,当再次刷新时,就是POP

useOutLet()

找出当前组件中的渲染的嵌套路由

useResolvedPath()

类似 useLocation()

const resolvePath = useResolvedPath("/func/id=21");

console.log(resolvePath);

react-router v6

分别使用类组件和函数式组件来记录

嵌套路由

import Home from "@/pages/Home";
import { Navigate, RouteObject } from "react-router-dom";
import Artcle from "@/pages/Home/Artcle";
import Details from "@/pages/Home/Artcle/Details";
import React, { lazy } from "react";
import Login from "@/pages/Login";
import Profile from "@/pages/Home/User/Profile/index";
import AccountSetting from "@/pages/Home/User/AccountSetting";
import { NotFount } from "@/pages/Error/404";
import ModifyPhone from "@/pages/Home/User/AccountSetting/ModifPhone";
import ModifyEmail from "@/pages/Home/User/AccountSetting/ModifyEmail";
import ModifyPwd from "@/pages/Home/User/AccountSetting/ModifyPwd";
import AccountWriteOff from "@/pages/Home/User/AccountSetting/AccountWriteOff";
import MyArtcleList from "@/pages/Home/User/MyArtcleList";
import ShowLoginHistory from "@/pages/Home/User/AccountSetting/ShowLoginHistory";
import { createBrowserRouter, createHashRouter } from "react-router-dom";
import User from "@/pages/Home/User";
import Call from "@/pages/Test/Call";
import FuncComponent from "@/pages/Test/FuncComponent";
import ClassComponent from "@/pages/Test/ClassComponent";
import ReduxTest from "@/pages/Test/ReduxTest/ReduxTest";
const lazyRouter = (
jsxCom: JSX.Element // 路由懒加载
) => <React.Suspense fallback={<h1>加载中</h1>}>{jsxCom}</React.Suspense>;

const router = createBrowserRouter([
{
path: "/",
element: <Navigate to={"/login"} />,
},

{
path: "/home",
element: <Home />,
children: [
{
path: "artcle",
element: <Artcle />,
},
{
path: "artcle:artcleId",
element: <Details />,
},
{
//个人中心
path: "user",
element: <User />,
children: [
{
path: "accountSetting",
element: <Navigate to={"/home/user"} replace />,
},
//内容管理
{
path: "myArtcleList",
element: <MyArtcleList />,
},
// 个人资料
{
path: "profile",
element: <Profile />,
},
],
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "/404",
element: <NotFount />,
},
{
path: "/*",
element: <NotFount />,
},
]);

export default router;

需要在父路由中使用<Outlet/>来指定路由出口

嵌套路由中的默认路由,

需要添加index:true,并且不应该写path:xxx

{
//个人中心
path: "user",
element: <User />,
children: [
// 账号设置
{
// path: "accountSetting",
element: <AccountSetting />,
children: [
{
index: true,
element: React.createElement(() => <h1>hello</h1>),
},
{
path: "phone/modify/:userId",
element: <ModifyPhone />,
},
{
path: "email/modify/:userId",
element: <ModifyEmail />,
},
{
path: "pwd/modify/:userId",
element: <ModifyPwd />,
},
{
path: "account/writeOff/:userId",
element: <AccountWriteOff />,
},
{
path: "showLoginHistory/:userId",
element: <ShowLoginHistory />,
},
],
},
{
path: "accountSetting",
element: <Navigate to={"/home/user"} replace />,
},
//内容管理
{
path: "myArtcleList",
element: <MyArtcleList />,
},
// 个人资料
{
path: "profile",
element: <Profile />,
},
],
},

路由参数

params 参数

router.tsx

定义 FuncComponent 组件需要接收的参数 id

image-20221226210157967

在 Call 组件分别调用函数式组件和类组件。路由跳转的时候将 id 传递给 FuncComponent 路由

<Link to={'/func/2'}><Button type='primary'>GO FUNC</Button></Link>|
<Link to={'/class/2'}><Button type="primary">GO CLASS</Button></Link>

  1. 函数式组件 FuncComponent

    使用 hooks 为 react-router-domuseParams()

const params = useParams();
console.log(params.id);
  1. 类组件 ClassComponent

    // TODO 待查资料

search 参数

不需要路由参数占位

image-20221226211334871

在 Call 组件分别调用函数式组件和类组件。路由跳转的时候将 id 传递给FuncComponent路由

<Link to={'/func?id=21'}><Button type='primary'>GO FUNC</Button></Link>|
<Link to={'/class?id=21'}><Button type="primary">GO CLASS</Button></Link>
  1. 函数式组件FuncComponent

    使用 hooks 为 react-router-domuseSearchParams()

const [search, setSearch] = useSearchParams();
console.log(search.get("id"));

setSearch()方法可以设置参数 id 的值:当点击 setSearch按钮时,路径参数的 id 变为 34

export default function FuncComponent() {
const [search, setSearch] = useSearchParams();
console.log(search.get("id"));
return (
<div>
<Button type="primary" onClick={() => setSearch("id=34")}>
setSearch
</Button>
</div>
);
}
  1. 类组件ClassComponent

    // TODO 待查资料

state 参数

不需要路由参数占位

image-20221226211334871

在 Call 组件分别调用函数式组件和类组件。路由跳转的时候将 id 传递给 FuncComponent 路由

<Link to={'/func'}><Button type='primary'>GO FUNC</Button></Link>|
<Link to={'/class'}><Button type="primary">GO CLASS</Button></Link>
  1. 函数式组件 FuncComponent

    使用 hooks 为react-router-dom的 useLocation()

const location = useLocation();
console.log(location.state.id);

location 的属性微信截图_20221226212901

可以通过解构赋值来获取 state 中的 id 属性。

const { state: { id } } = useLocation()

也可以通过.的方式获取

  1. 类组件 ClassComponent

    // TODO 待查资料

编程式导航

编程式导航(不需要点击操作的路由跳转)。

html 标签react-ROuter6React-Router-5
<a><Link><Link>
<a><NavLink><NavLink>
<Navigate>

<Navigate> 需要写在 html 标签中

js中使用react-router-domuseNavigate()

const navigate = useNavigate()

navigate 可以接收两个参数toNavigateOptions类型的options

navigate (to,options)

/**
* The interface for the navigate() function returned from useNavigate().
*/
export interface NavigateFunction {
(to: To, options?: NavigateOptions): void;
(delta: number): void;
}

options 的类型有 4 个属性

export interface NavigateOptions {
replace?: boolean;
//传递给组件的state,可以获取值
state?: any;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
}

使用栗子,跳转到/func路由时,使用state 参数携带参数 id=22

import React from "react";
import { NavigateOptions, useLocation } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { Button } from "antd";
export default function FuncComponent() {
const navigate = useNavigate();
const option: NavigateOptions = {
replace: false,
state: {
id: 222,
},
};
return (
<div>
<Button onClick={() => navigate("/func", option)}>GO FUNC</Button>
</div>
);
}

FuncComponent

import React from "react";
import { useLocation } from "react-router-dom";
export default function FuncComponent() {
const location = useLocation();
console.log(location.state);
return <div></div>;
}

tips:使用 param 参数和 search 参数,可以直接使用navigate('/func/2')或者navigate('/func?id=2')传递

路由配置

React Router 6 中的 5 种路由配置方式

  1. 使用数组配置

使用 createBrowserRoutercreateHashRouter 函数,以数组形式定义嵌套路由。这种方式类似于 Vue Router 中的路由配置。以下是一个示例代码:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Contact from "./Contact";

const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "/about",
element: <About />,
},
{
path: "/contact",
element: <Contact />,
},
]);

function App() {
return <RouterProvider router={router} />;
}

export default App;

在这个示例中,我们使用 createBrowserRouter 函数创建了一个路由器,并传入了一个包含多个路由对象的数组。每个路由对象都包含 path 和 element 两个属性,分别表示路由路径和对应的组件。最后,我们使用 RouterProvider 组件将路由器提供给整个应用。

  1. 使用 React.lazy 和 Suspense 进行代码分割 使用 React.lazySuspense 可以实现路由组件的延迟加载,优化应用的初始加载时间。以下是一个示例代码:

    import {
    createBrowserRouter,
    RouterProvider,
    Suspense,
    } from "react-router-dom";
    import Home from "./Home";
    const About = React.lazy(() => import("./About"));
    const Contact = React.lazy(() => import("./Contact"));

    const router = createBrowserRouter([
    {
    path: "/",
    element: <Home />,
    },
    {
    path: "/about",
    element: (
    <Suspense fallback={<div>Loading...</div>}>
    <About />
    </Suspense>
    ),
    },
    {
    path: "/contact",
    element: (
    <Suspense fallback={<div>Loading...</div>}>
    <Contact />
    </Suspense>
    ),
    },
    ]);

    function App() {
    return <RouterProvider router={router} />;
    }

    export default App;

    在这个示例中,我们使用 React.lazy 函数动态导入了 AboutContact 两个组件,并使用 Suspense 组件为它们添加了一个加载中的提示。这样一来,这两个组件只有在真正需要渲染时才会被加载,提高了应用的初始加载速度。

  2. 使用对象配置

使用 createRoutesFromElements 函数和 Route 组件,以 JSX 语法定义路由配置对象。这种方式类似于 Vue Router 中使用对象配置路由。以下是一个示例代码:

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { createRoutesFromElements, Route } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Contact from './Contact';

const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Home />}>
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
</Route>
)
);

function App() {

在这个示例中,我们使用 createRoutesFromElements 函数创建了一个路由配置对象,并使用 JSX 语法定义了路由的嵌套关系。最终,我们使用 createBrowserRouter 函数创建了一个路由器,并传入了路由配置对象。

  1. 使用函数动态创建路由配置

使用 useRoutes hook 和 React.createElement,可以在函数组件中动态创建路由配置对象,实现高度灵活和可编程的路由配置。以下是一个示例代码:

import { useRoutes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Contact from "./Contact";

function RouterConfig() {
const routes = [
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
{ path: "/contact", element: <Contact /> },
];

const element = useRoutes(routes);

return element;
}

function App() {
return <RouterConfig />;
}

export default App;

在这个示例中,我们定义了一个 RouterConfig 函数组件,在其中使用 useRoutes hook 动态创建了路由配置对象。最终,我们将 RouterConfig 组件渲染到应用中,实现了路由的配置。

  1. 从服务器加载路由配置数据

将路由配置数据存储在服务器上,在应用启动时从服务器获取该数据,然后使用 useRoutes hook 动态生成路由配置。以下是一个示例代码:

import { useRoutes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Contact from "./Contact";

function RouterConfig() {
const [routes, setRoutes] = useState([]);

useEffect(() => {
fetch("/api/routes")
.then((res) => res.json())
.then((data) => setRoutes(data));
}, []);

const element = useRoutes([
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
{ path: "/contact", element: <Contact /> },
...routes,
]);

return element;
}

function App() {
return <RouterConfig />;
}

export default App;

在这个示例中,我们使用 useStateuseEffect hook 从服务器获取路由配置数据,并将其存储在 routes 状态中。随后,我们使用 useRoutes hook 动态生成了路由配置,并将其渲染到应用中。

Redux

状态管理 js 库,Vue 也可以使用,如 vuex 或者 pinia,redux。

redux 管理多个组件的共享数据

image-20221226224110447

Redux 三大原则

理解好 Redux 有助于我们更好的理解接下来的 React -Redux

第一个原则

单向数据流:整个 Redux 中,数据流向是单向的

UI 组件 ---> action ---> store ---> reducer ---> store

第二个原则

state 只读:在 Redux 中不能通过直接改变 state ,来控制状态的改变,如果想要改变 state ,则需要触发一次 action。通过 action 执行 reducer

第三个原则

纯函数执行:每一个 reducer 都是一个纯函数,不会有任何副作用,返回是一个新的 state,state 改变会触发 store 中的 subscribe

使用原生 redux

yarn add redux

image-20230115234229792

src 下建立 redux 或者 store 文件夹。本例建立的是 redux

index.ts

// import { createStore } from 'redux'
import { legacy_createStore as createStore } from "redux";
import countReducer from "./count_reducer";
//store 相当于老板 ,reducer相当于后厨
//createStore过时了,学习新的
const store = createStore(countReducer);

export default store;

count_reducer.ts

/**
* 后厨
* 1、该文件时用户创建一个为count服务的reducer reducer本质是个函数
*
* 2、reducer函数的两个参数 之前的状态(preState) ,动作对象(action)
* action的数据结构:{type:string,data :any}
*/
// 1. 定义初始化值
export type ActionType = {
type: string;
data: number;
};
function countReducer(preState: number = 0, action: ActionType) {
console.log(preState, action);

//从action对象获取type、data
const { type, data } = action;
// 根据type决定如何加工数据
switch (type) {
//如果是加
case "increment":
return preState + data;
//如果是减
case "decrement":
return preState - data;
default:
//初始化
return 0;
}
}
export default countReducer;

使用

legacy_createStore暴露 4 个方法

image-20230115233909832

将 action 转发给 store,由 store 交给 reducer 处理,之后 reducer 返回新数据给 store,sotre 在将新数据通过getState()方法获取到

import { store } from '@/store';
const App: React.FC = () => {
const [reduxVar, setReduxVar] = useState<number>()
//组件挂载完成时获取store的初始值
useEffect(() => {
setReduxVar(store.getState())
}, []);
const plusRedux = () => {
//+2
store.dispatch({ type: "increment", data: 2 })
}
const reduceRedux = () => {
//-3
store.dispatch({ type: "decrement", data: 3 })
}
store.subscribe(() => {
setReduxVar(store.getState())
})

return <React.Fragment>
<div>{reduxVar} </div>
<div><Button onClick={plusRedux}>plus</Button>|<Button onClick={reduceRedux}>reduce</Button></div>
</React.Fragment>;
}

使用 react-redux

yarn add react-redux

使用 reduxjs/toolkit

yarn add @reduxjs/toolkit

react 之 useNavigate() 三种的传参方法和接收方法

1.直接传入路径字符串:

const navigate = useNavigate();
navigate('/destination?name=xxx');
接收: const [searchParams] = useSearchParams();

// 使用 URLSearchParams 提供的方法来获取参数
const name = searchParams.get('name');

2.使用 Location State:

传参
const navigate = useNavigate();
navigate('/destination', { state: { a:10 } });
接收:

const {state} = useLocation();

state里面就是{ a:10 }

3.使用查询字符串:



传参

const navigate = useNavigate();
navigate('/destination/12');

需要对路由进行配置
path:'/destination:/id'
接收:

const { id } = useParams();


其他

push 和 reaplace 区别

router.push(location) 想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL 当你点击 时,这个方法会在内部调用,所以说,点击 等同于调用 router.push(…)。

replace 模式是替换模式,会替换掉栈顶的路由 设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录。即使点击返回按钮也不会回到这个页面。

开启方法:

\<Link replace={true} to="/about">About\</Link>

遇到的问题

useState 数据更新不同步

思路:1、检查是否有异步 ajax 请求,有的话改成同步

await axios.get(`/api/season/${seasonId}`, {}).then((data) => {
const edit = data.data.data;
setEditSeason(edit);
});
setOpenModal(true);

React 18 并发模式下的提示

警告信息:[antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. console.<computed> @ index.js:1

代码:

A.tsx,请求一定会出错

useEffect(() => {
fetch(`http://localhost:8080/season/list/1/6`)
.then((response) => {
return response.json();
})
.then((data) => {
setData(data.records);
setCurrent(data.current);
setTotalNum(data.total);
})
.catch((err) => {
openNotification("bottomRight");
});
}, []);

触发 A.tsx 后,未等错误信息返回直接切换到 B.tsx,会出现上面的警告

export default function Test() {
const a = 2;
return <h1> {a}</h1>;
}

解决方法 1

useEffect(() => {
let isMounted = true;
fetch(`http://localhost:8080/season/list/1/6`)
.then((response) => {
return response.json();
})
.then((data) => {
if (isMounted) {
// 只有在组件没有卸载时才更新状态
setData(data.records);
setCurrent(data.current);
setTotalNum(data.total);
}
})
.catch((err) => {
if (isMounted) {
// 只有在组件没有卸载时才调用 openNotification
openNotification("bottomRight");
}
});
return () => {
isMounted = false; // 在组件卸载时将标志设为 false
};
}, []);

解决方法 2

使用了 AbortControllersignal 来控制请求的取消。在组件卸载时,调用了 controller.abort() 方法来取消未完成的请求。在 catch 分支中,我们通过判断错误的名称来区分请求是否应该被处理。如果是 AbortError,则说明请求被取消,不需要进行错误处理。

useEffect(() => {
const controller = new AbortController();
fetch(`http://localhost:8080/season/list/1/6`, { signal: controller.signal })
.then((response) => {
return response.json();
})
.then((data) => {
setData(data.records);
setCurrent(data.current);
setTotalNum(data.total);
})
.catch((err) => {
if (err.name === "AbortError") {
// 请求被取消,不需要处理
return;
}
openNotification("bottomRight");
});

return () => {
console.log("组件卸载,放弃请求");

controller.abort(); // 在组件卸载时取消请求
};
}, []);

关于 className 使用的问题

当 scss 文件中有一个名为 isLandscape 的样式,同时组件中也有一个 isLandscape 的变量,那么当使用 isLandscape 变量做 className 判断时会优先使用 scss 中的 isLandscape 样式,然后再去使用 isLandscape 变量去做判断

例子 横竖屏切换 index.scss 文件

.isLandscape {
@include screen();
}

//竖屏 vertical screen
@mixin vertical() {
}

//横屏 Cross screen
@mixin cross() {
//以左上角为原点,旋转90度
transform-origin: left top;
transform: rotate(90deg);
//设置最小高度和宽度
// min-height: 100vw;
// min-width: 100vh;
//设置位置
// position: absolute;
//旋转完毕要将视图往左偏移
// left: 100vw;
// overflow-y: auto;
}

react 组件

  const [isLandscape, setIsLandscape] = useState(false);
<div className={isLandscape ? '' : ''}>

isLandscape 改变,触发页面刷新,div 的 className 会根据 isLandscape 变量进行判断,当条件都为空字符串时。会使用 scss 中的 isLandscape。

const [isLandscape, setIsLandscape] = useState(false);
<div className={isLandscape ? 'crossScreen' : 'verticalScreen'}>

由于 crossScreen 是将页面旋转 90 度,再向左偏移一个屏幕宽度,所以这时候调用两次,就变成了旋转 180 度,左偏移了 2 个屏幕宽度

就会出现这种情况。一开始以为是 useState 变化了两次,才导致的这个原因