React18

news/2024/9/28 1:29:37

0x01 React 基础

(1)概述

  • React 框架由 Meta(原 Facebook)研发并发布
  • 用于构建 Web 和原生交互界面的库
  • 优点:
    • 使用组件化的开发方式,性能较优
    • 具有丰富生态,并且支持跨平台

(2)创建开发环境

a. 第一个工程

需要提前安装并配置好 Node.js v16+、npm v9+ 等工具

  1. 使用命令 npm install -g create-react-app 安装创建 React 应用的工具

    • create-react-app 是由 Webpack 构建的、用于快速创建 React 开发环境的工具
    • 以下内容使用的是 React v18.3
  2. 使用命令 create-react-app react-app 创建一个名为 react-app 的应用

  3. 使用命令 cd react-app 进入应用目录

    • 目录文件说明:

      • node_modules:依赖目录

      • public:静态资源目录

        • favicon.ico:图标文件
        • index.html:页面文件
      • src:代码资源目录

        • App.js:应用文件

          function App() {return <div className="App">Hello, react!</div>;
          }export default App;
        • index.js:入口文件

          // React 必要的核心依赖(包)
          import React from "react";
          import ReactDOM from "react-dom/client";// 导入应用组件的文件
          import App from "./App";// 将应用组件渲染到页面上
          const root = ReactDOM.createRoot(document.getElementById("root")); // 将 index.html 中 id 为 root 的标签创建为 React 根组件
          root.render(<App />); // 将 App 组件渲染到根组件中
      • .gitignore:Git 忽略配置

      • package.json / package-lock.json:工程配置文件

      • README.md:工程说明文件

    • 业务规范项目目录

      目录 作用
      apis 接口
      assets 静态资源
      components 组件
      pages 页面
      router 路由
      store Redux 状态管理
      utils 工具函数
  4. 使用命令 npm start 运行 React 应用

  5. 访问 http://localhost:3000/ 查看默认 React 应用页面

b. 开发工具及插件

  • 编辑器建议使用 VSCode,推荐其中添加以下插件:
    • ES7+ React/Redux/React-Native snippets
    • ESLint
    • Prettier
    • Simple React Snippets
    • Typescript React code snippets
    • VSCode React Refactor
  • 浏览器中关于 React 调试的扩展:
    • 安装 Chrome 扩展
      • 如果无法访问,则可以尝试该链接:https://www.crx4chrome.com/crx/3068/
    • 安装 Firefox 扩展
    • 安装 Edge 扩展

(3)JSX

a. 概述

  • JSX(JavaScript and XML)是 Javascript 语法扩展,可以在 Javascript 文件中书写类似 HTML 的标签
    • 组件使用 JSX 语法,从而使渲染逻辑和标签共同存在于组件中
  • JSX 看起来和 HTML 很像,但它的语法更加严格并且可以动态展示信息
    • JSX 转化器:https://transform.tools/html-to-jsx
  • JSX 的优点在于:既可以使用 HTML 的声明式模板写法,还可以使用 JS 的可编程能力
  • JSX 代码需要通过 Babel 进行编译,借助 @babel/plugin-transform-react-jsx

b. JS 表达式

  • 在 JSX 中,使用 {} 识别 JS 表达式(在 App.js 中编辑)

    • 传递字符串

      function App() {return <div>{"Hello, react!"}</div>;
      }export default App;
    • 使用变量

      const number = 123;function App() {return <div className="App">{number}</div>;
      }export default App;
    • 使用对象

      const object = {backgroundColor: "skyblue",color: "white",padding: "2rem",
      };function App() {return (<div className="App" style={object}>Hello, react!</div>);
      }export default App;
    • 调用函数与方法

      function getString() {return "Today is ";
      }function App() {return <div className="App">{getString()} {new Date().toLocaleDateString()}</div>;
      }export default App;
  • if 语句、switch 语句、变量声明等属于语句,而非 JS 表达式,因此不能出现在 JSX 的 {}

c. 列表渲染

  • 使用数组的 map() 方法
  • 标签其中必须设置属性 key,其赋值使用独一无二的数字或字符串
const list = [{ name: "Alex", age: 18 },{ name: "Bob", age: 20 },{ name: "Charlie", age: 22 },
];function App() {return (<div className="App"><ul>{list.map((item, index) => (<li key={index}>{item.name} - {item.age}</li>))}</ul></div>);
}export default App;

d. 条件渲染

  • 使用逻辑与运算符 && 以及三元表达式 ?: 实现基础条件渲染

    const flag = true;function App() {return (<div className="App">运算符: {flag && <span>flag is true</span>} <br />表达式: {!flag ? <span>flag is true</span> : <span>flag is false</span>}</div>);
    }export default App;
  • 当条件较为复杂时,使用函数来处理

    function judge(condition) {if (condition === 0) {return <span>空</span>;} else if (condition === 1) {return <span>唯一</span>;} else {return <span>两个及以上</span>;}
    }function App() {return (<div className="App"><p>0: {judge(0)}</p><p>1: {judge(1)}</p><p>2: {judge(2)}</p><p>3: {judge(3)}</p></div>);
    }export default App;

e. 事件绑定

  • on[事件名称] = {事件处理程序},如点击事件:

    function App() {const handleClick = () => {alert("Clicked");};return (<div className="App"><button onClick={handleClick}>Click Me</button></div>);
    }export default App;
  • 事件对象参数

    function App() {const handleClick = (e) => {console.dir(e);};return (<div className="App"><button onClick={handleClick}>Click Me</button></div>);
    }export default App;
  • 自定义参数

    • 需要函数引用,而不能直接调用函数
    function App() {const handleClick = (value) => {alert(`name is ${value}`);};return (<div className="App"><button onClick={() => handleClick("Alex")}>Click Me</button></div>);
    }export default App;
    • 同时传递事件对象和自定义参数

      function App() {const handleClick = (e, value) => {console.dir(e);alert(`name is ${value}`);};return (<div className="App"><button onClick={(e) => handleClick(e, "Alex")}>Click Me</button></div>);
      }export default App;

(4)组件

a. 组件使用

  • 组件是 React 的核心概念之一,是构建用户界面(UI)的基础

    • 组件是独立的 UI 片段,一个或多个组件构建成 React 应用
    • 组件本质上是可以任意添加标签的 Javascript 函数
    // 自定义组件
    function Paragraph() {return <p>This is a paragraph.</p>;
    }function App() {return (<div><h1>This is a heading.</h1>// 使用自定义组件<Paragraph /><Paragraph /><Paragraph /></div>);
    }export default App;

b. 组件样式

  • 组件基础样式控制有两种方案:

    1. 行内样式

      <div style={{ color: 'red' }}>content</div>
      
    2. class 类名控制

      /* index.css */
      .content {color: red;
      }
      
      // App.js
      import './index.css'function App() {return <div className='content'>content</div>
      }
      
      • 推荐使用 class 类名控制
  • 使用 classnames 优化类名控制

    • classnames 是 JavaScript 库,可以通过条件动态控制 class 类名的显示

    • 使用命令 npm install classnames 安装

    • 举例:

      import "./index.css";import { useState } from "react";
      import classNames from "classnames";function App() {const [flag, setFlag] = useState(false);const handleClick = () => {setFlag(true);};return (<div><p className={classNames("content", { active: flag })}>文本内容</p><button onClick={handleClick}>点击显示文本内容</button></div>);
      }export default App;

(5)Hooks

  • React 钩子(hook)的使用规则
    • 只能在组件中或其他自定义 Hook 中调用
    • 只能在组件的顶层调用,不能嵌套在 iffor、其他函数中

a. useState

  • useState 是一个 React Hook,用于向组件添加状态变量,从而控制并影响组件的渲染结果

    • 数据驱动视图:当状态变量变化,组件的视图也会跟着变化
    • useState():一个函数,其返回值是一个数组
  • 语法:const [var, func] = useState(initValue)

    • 基于 useState() 函数的返回值进行解构赋值
    • var:状态变量
    • func:修改状态变量的函数方法
    • initValue:状态变量初始值
  • 举例:

    import { useState } from "react";function App() {const [count, setCount] = useState(0);const handleClick = () => {setCount(count + 1);};return (<div><p>{count}</p><button onClick={handleClick}>+ 1</button></div>);
    }export default App;
  • 状态不可变规则:即状态是只读的,不可被修改,只能被替换

    • 简单类型,如上述案例

    • 复杂类型,如对象:

      import { useState } from "react";function App() {const [form, setForm] = useState({username: "Alex",password: "123456"});const handleClick = () => {setForm({...form,username: "Bob",password: "654321"});};return (<div><p>用户名: {form.username}</p><p>密码: {form.password}</p><button onClick={handleClick}>修改</button></div>);
      }export default App;
  • 使用 useState 控制表单状态

    • 使用 onChange 监测输入的内容是否发生改变,从而修改 content 的值
    import { useState } from "react";function App() {const [content, setContent] = useState("");const handleChange = (value) => {setContent(value);};return (<div><p>输入的内容: {content}</p><inputtype="text"onChange={(e) => {handleChange(e.target.value);}}/></div>);
    }export default App;

b. useReducer

  • useRuducer 作用与 useState 类似,用于管理相对复杂的状态数据

  • 举例:

    import { useReducer } from "react";// 1. 定义 reducer 函数, 根据不同的 action 返回不同的新状态
    function reducer(state, action) {switch (action.type) {case "increment":return state + 1;case "decrement":return state - 1;default:return state;}
    }function App() {// 2. 调用 useReducer 并传入 reducer 函数和初始值const [state, dispatch] = useReducer(reducer, 0);return (<div>{/* 3. 事件触发 dispatch 并分派 action 对象 */}<button onClick={() => dispatch({ type: "decrement" })}>-1</button><span>{state}</span><button onClick={() => dispatch({ type: "increment" })}>+1</button></div>);
    }export default App;

c. useRef

  • useRef 用于在 React 组件中创建和操作 DOM

  • 举例:

    import { useRef } from "react";function App() {const inputRef = useRef(null); // 1. 创建 Ref 对象function handleClick() {console.dir(inputRef.current); // 3. 获取 DOM 节点}return (<div><inputtype="text"ref={inputRef} // 2. 将 Ref 对象与 input 元素绑定/><button onClick={handleClick}>获取 DOM</button></div>);
    }export default App;

d. useEffect

  • useEffect 用于创建由渲染触发的操作(不是由事件触发),如发送 Ajax 请求、更改 DOM 等

  • 语法:useEffect(() => {}, [])

    • 回调函数称为“副作用函数”,其中是需要执行的操作
    • 数组可选,其中是监听项,作为空数组时副作用函数仅在组件渲染完毕之后执行一次
  • 举例:

    import { useEffect, useState } from "react";function App() {const [data, setData] = useState("");useEffect(() => {async function getData() {const response = await fetch("https://api.ipify.org?format=json");const data = await response.json();setData(data.ip);}getData();}, []);return <div>IP address: {data}</div>;
    }export default App;
  • 监听项数组影响副作用函数

    监听项 副作用函数执行时机
    组件初始渲染和更新时执行
    空数组 仅在初始渲染时执行
    特定监听项 组件初始渲染和监听项变化时执行
    • 无监听项

      import { useEffect, useState } from "react";function App() {const [count, setCount] = useState(0);useEffect(() => {console.log("副作用函数执行");});return (<div><span>count: {count}</span><button onClick={() => setCount(count + 1)}>+1</button></div>);
      }export default App;
    • 空数组

      import { useEffect, useState } from "react";function App() {const [count, setCount] = useState(0);useEffect(() => {console.log("副作用函数执行");}, []);return (<div><span>count: {count}</span><button onClick={() => setCount(count + 1)}>+1</button></div>);
      }export default App;
    • 特定监听项

      import { useEffect, useState } from "react";function App() {const [count, setCount] = useState(0);useEffect(() => {console.log("副作用函数执行");}, [count]);return (<div><span>count: {count}</span><button onClick={() => setCount(count + 1)}>+1</button></div>);
      }export default App;
  • 当组件卸载时,需要清理副作用函数

    useEffect(() => {// 实现副作用操作逻辑return () => {// 清除副作用操作逻辑}
    },[])
    
    • 举例:清除定时器

      import { useEffect, useState } from "react";function Child() {useEffect(() => {const timer = setInterval(() => {console.log("定时器");}, 500);return () => {clearInterval(timer);console.log("清除定时器");};}, []);return <div>子组件</div>;
      }function App() {const [show, setShow] = useState(true);return (<div>{show && <Child />}<button onClick={() => setShow(false)}>卸载子组件</button></div>);
      }export default App;

e. useMemo

  • useMemo 用于在组件每次重新渲染的时候缓存计算的结果

  • 举例:

    import { useMemo, useState } from "react";function fibonacci(n) {console.log("斐波那契数列计算");if (n < 3) {return 1;}return fibonacci(n - 2) + fibonacci(n - 1);
    }function App() {const [count1, setCount1] = useState(0);const [count2, setCount2] = useState(0);const result = useMemo(() => {return fibonacci(count1);}, [count1]);console.log("组件重新渲染");return (<div><button onClick={() => setCount1(count1 + 1)}>count1: {count1}</button><button onClick={() => setCount2(count2 + 1)}>count2: {count2}</button>{result}</div>);
    }export default App;

f. useCallback

  • useCallback 用于在组件多次重新渲染时缓存函数

  • 举例:

    import { memo, useCallback, useState } from "react";const Comp = memo(function Comp({ prop }) {console.log("子组件重新渲染");return (<input type="number" onChange={(e) => prop(parseInt(e.target.value))} />);
    });function App() {const [count, setCount] = useState(0);const changeHandler = useCallback((value) => {setCount(value);}, []);return (<div>{count}<button onClick={() => setCount(count + 1)}>+1</button><Comp prop={changeHandler} /></div>);
    }export default App;

g. 自定义 Hook

  • 自定义 Hook 是以 use 为前缀的函数方法

  • 通过自定义 Hook 可以实现逻辑的封装与复用

  • 举例:

    import { useState } from "react";function useToggle() {const [value, setValue] = useState(true);const toggle = () => setValue(!value);return [value, toggle];
    }function App() {const [value, toggle] = useToggle();return (<div><button onClick={toggle}>Toggle</button>{value && <div>Div</div>}</div>);
    }export default App;

(6)组件通信

  • 组件通信是指组件之间的数据传递
    • 不同的组件嵌套方式有不同的数据传递方法,如父传子、子传父、兄弟间等

a. 父传子

  • 通过 props 实现父传子组件通信步骤:

    1. 父组件发送数据:在子组件标签上绑定属性
    2. 子组件接受数据:子组件通过 props 参数接收数据
    function Child(props) {console.log(props);return <p>Child Comp</p>
    }function App() {const name = "Alex"return (<div><Child name={name} /></div>);
    }export default App;
  • props 可以传递任意类型的数据,如数字、字符串、数组、对象、方法、JSX

  • props 是只读对象,数据只能在父组件修改

  • 当父组件中把内容嵌套在子组件中时,props 会自动添加 children 属性

    function Child(props) {console.log(props);return <p>Child Comp</p>
    }function App() {return (<div><Child><span>Alex</span></Child></div>);
    }export default App;

b. 子传父

  • 通过调用父组件方法传递参数实现子传父组件通信

    function Child({ onHandle }) {const value = "Alex";return <button onClick={() => onHandle(value)}>发送</button>;
    }function App() {const getValue = (value) => console.log(value);return (<div><Child onHandle={getValue} /></div>);
    }export default App;

c. 兄弟间

  • 使用状态提升实现兄弟组件通信

    • 即通过子传父的方法将子组件 A 的数据传递到父组件,再通过父传子的方法将父组件接收的数据传递到子组件 B
    import { useState } from "react";function A({ onHandle }) {const value = "Alex";return (<label>A: <button onClick={() => onHandle(value)}>发送</button></label>);
    }function B(props) {return <div>B: {props.value}</div>;
    }function App() {const [value, setValue] = useState("");const getValue = (value) => setValue(value);return (<div><A onHandle={getValue} /><B value={value} /></div>);
    }export default App;

d. 跨层级(任意组件间)

  • 使用 Context 机制实现跨层级组件通信步骤:

    1. 使用 createContext 方法创建上下文对象
    2. 在顶层组件中,通过 Provider 传递(提供)数据
    3. 在底层组件中,通过 useContext 方法接收(消费)数据
    import { createContext, useContext } from "react";const ctx = createContext();function Child() {const name = useContext(ctx);return <span>Child: {name}</span>;
    }function Parent() {return <Child />;
    }function App() {const name = "Alex";return (<div><ctx.Provider value={name}><Parent /></ctx.Provider></div>);
    }export default App;
  • 该方法可用于任意组件间进行组件通信

0x02 Redux

(1)概述

  • Redux 是 React 最常用的集中状态关联工具,可以独立于框架运行

  • 快速上手案例:

    <!DOCTYPE html>
    <html lang="en"><body><button id="decrement">-1</button><span>0</span><button id="increment">+1</button><script>// 第一步function reducer(state = { count: 0 }, action) {if (action.type === "DECREMENT") {return { count: state.count - 1 };}if (action.type === "INCREMENT") {return { count: state.count + 1 };}return state;}// 第二步const store = Redux.createStore(reducer);// 第三步store.subscribe(() => {console.log("数据改变");document.querySelector("span").textContent = store.getState().count;  // 第五步});// 第四步const decrement = document.getElementById("decrement");decrement.addEventListener("click", () =>store.dispatch({ type: "DECREMENT" }));const increment = document.getElementById("increment");increment.addEventListener("click", () =>store.dispatch({ type: "DECREMENT" }));</script></body>
    </html>
    
    1. 定义 reducer 函数
      • 作用:根据不同的 action 对象,返回不同的、新的 state
      • state:管理数据初始状态
      • action:对象 type 标记
    2. 生成 store 实例
    3. 订阅数据变化
    4. 通过 dispatch 提交 action 更改状态
    5. 通过 getState 方法获取最新状态数据并更新到视图
  • 在 Chrome 浏览器中可以使用 Redux DevTools 插件对 Redux 进行调试

(2)结合 React

a. 配置环境

  • 在 React 中使用 Redux 前,需要安装 Redux Toolkitreact-redux

    • Redux Toolkit(RTK)是官方推荐编写 Redux 逻辑的方式,简化书写方式,包括:

      • 简化 store 配置方式
      • 内置 immer 支持可变式状态修改
      • 内置 thunk 更好地异步创建
    • react-redux 是用于链接 React 组件和 Redux 的中间件

      graph LR Redux--获取状态-->React组件 --更新状态-->Redux
    • 使用命令 npm install @reduxjs/toolkit react-redux 安装

  • 安装完成后,在 src 目录下新建 store 目录

    graph TB store-->modules & index.js modules-->subStore.js & ...
    • store 目录是集中状态管理的部分
    • 其中新建 index.js,是入口文件,组合子 store 模块
    • 其中新建 modules 目录,用于包括多个子 store 模块

b. 实现 counter

  1. 在 src/store/modules 中创建 counterStore.js

    import { createSlice } from "@reduxjs/toolkit";const counterStore = createSlice({name: "counter",initialState: {// 初始状态数据count: 0,},reducers: {// 修改数据的同步方法increment(state) {state.count++;},decrement(state) {state.count--;},},
    });// 解构出创建 action 对象的函数
    const { increment, decrement } = counterStore.actions;// 获取 reducer 函数
    const counterReducer = counterStore.reducer;// 导出创建 action 对象和 reducer 函数
    export { increment, decrement };
    export default counterReducer;
  2. 修改 src/store/index.js

    import { configureStore } from "@reduxjs/toolkit";
    import counterReducer from "./modules/counterStore";// 创建根 store 来组合子 store 模块
    const store = configureStore({reducer: {counter: counterReducer,},
    });export default store;
  3. 修改 src/index.js,将 store 导入 React 组件

    import store from "./store";
    import { Provider } from "react-redux";// ...root.render(<Provider store={store}><App /></Provider>
    );
  4. 修改 src/App.js,在组件中使用 store,通过 useSelector 钩子函数

    • 该函数将 store 中的数据映射到组件中
    import { useSelector } from "react-redux";function App() {const { count } = useSelector(state => state.counter);return <div>{count}</div>;
    }export default App;
  5. 修改 src/App.js,使用 useDispatch 钩子函数修改数据

    • 该函数生成提交 action 对象的 dispatch 函数
    import { useDispatch, useSelector } from "react-redux";
    import { decrement, increment } from "./store/modules/counterStore"; // 导入创建 action 对象的方法function App() {const { count } = useSelector((state) => state.counter);const dispatch = useDispatch(); // 得到 dispatch 函数return (<div>{/*调用 dispatch 提交 action 对象*/}<button onClick={() => dispatch(decrement())}>-</button><span>{count}</span><button onClick={() => dispatch(increment())}>+</button></div>);
    }export default App;

c. 提交 action 传参

  • 以上述案例为例,为了实现点击不同按钮可以直接把 count 的值修改为指定数字,需要在提交 action 对象时传递参数

  • 原理:在 reducer 的同步修改方法中添加 action 对象参数,在调用 actionCreater 方法时传参,其中参数会被传递到对象的 payload 属性上

  • 修改上述案例:

    1. 修改 src\store\modules\counterStore.js,创建 change 方法用于修改 count 变量到指定的值

      import { createSlice } from "@reduxjs/toolkit";const counterStore = createSlice({// ...reducers: {// ...change(state, action) {state.count = action.payload;},},
      });const { increment, decrement, change } = counterStore.actions;
      const counterReducer = counterStore.reducer;
      export { increment, decrement, change };
      export default counterReducer;
    2. 修改 src\App.js,在组件中使用

      import { useDispatch, useSelector } from "react-redux";
      import { change } from "./store/modules/counterStore";function App() {const { count } = useSelector((state) => state.counter);const dispatch = useDispatch();return (<div><span>{count}</span><button onClick={() => dispatch(change(10))}>to 10</button><button onClick={() => dispatch(change(100))}>to 100</button></div>);
      }export default App;

d. 异步状态操作

  • 异步修改需要单独封装一个函数,其中返回一个新函数,这个新函数可以:

    • 封装异步请求,并获取数据
    • 调用同步 actionCreater 传入异步数据生成 action 对象,并使用 dispatch 提交
  • 举例:src\store\modules\channelStore.js

    import { createSlice } from "@reduxjs/toolkit";const channelStore = createSlice({name: "channel",initialState: {channelList: [],},reducers: {setChannels(state, action) {state.channelList = action.payload;},},
    });const { setChannels } = channelStore.actions;
    const url = "";
    const fetchChannelList = () => {return async (dispatch) => {const res = await axios.get(url);dispatch(setChannels(res.data.channels));};
    };const reducer = channelStore.reducer;export { fetchChannelList };
    export default reducer;

0x03 React Router

(1)概述

  • React Router 实现客户端路由,使响应速度更快

  • 创建路由开发环境

    1. 使用命令 npm install react-router-dom 安装 React Router

    2. 修改 src\index.js

      // ...
      import { createBrowserRouter, RouterProvider } from "react-router-dom";// 1. 创建 router 实例对象并配置路由对应关系
      const router = createBrowserRouter([{path: "/login",element: <div>登录页</div>,},{path: "/register",element: <div>注册页</div>,},
      ]);const root = ReactDOM.createRoot(document.getElementById("root"));// 2. 路由绑定
      root.render(<RouterProvider router={router} />);
    3. 使用命令 npm start 启动项目

    4. 依次访问 http://localhost:3000/login 和 http://localhost:3000/register

(2)抽象路由模块

  1. 在 src 目录下新建 page 目录,其中包含各个页面,如 Login/index.js 和 Register/index.js

    // src\page\Login\index.js
    const Login = () => {return <div>登录页</div>;
    };export default Login;
    // src\page\Register\index.js
    const Register = () => {return <div>注册页</div>;
    };export default Register;
  2. 在 src 目录下新建 router 目录,其中新建 index.js,包含路由配置

    import Login from "../page/Login";
    import Register from "../page/Register";import { createBrowserRouter } from "react-router-dom";const router = createBrowserRouter([{path: "/login",element: <Login />,},{path: "/register",element: <Register />,},
    ]);export default router;
  3. 修改 src\index.js

    // ...
    import { RouterProvider } from "react-router-dom";
    import router from "./router";const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<RouterProvider router={router} />);
  4. 依次访问 http://localhost:3000/login 和 http://localhost:3000/register

(3)路由导航

  • 路由导航是指多个路由之间需要进行路由跳转,并且跳转过程中可能需要传参通信

  • 主要用两种导航方式:声明式导航和编程式导航

    • 声明式导航是指在模板中通过 Link 组件指定跳转的目标路由,如:

      // src\page\Login\index.js
      import { Link } from "react-router-dom";const Login = () => {return (<div><p>登录页</p><Link to="/register">前往注册页</Link></div>);
      };export default Login;

      此时,访问 http://localhost:3000/login 并点击链接即可跳转至 http://localhost:3000/register

    • 编程式导航是指通过 useNavigate 钩子得到方法,并通过调用方法以命令式的形式实现路由跳转,如:

      // src\page\Login\index.js
      import { useNavigate } from "react-router-dom";const Login = () => {const navigate = useNavigate();return (<div><p>登录页</p><button onClick={() => navigate("/register")}>前往注册页</button></div>);
      };export default Login;

      此时,访问 http://localhost:3000/login 并点击按钮即可跳转至 http://localhost:3000/register

(4)传递参数

  1. 基于编程式导航,使用 useSearchParams 钩子获取 URI 中的查询字符串,如:

    • src\page\Login\index.js

      import { useNavigate } from "react-router-dom";const Login = () => {const navigate = useNavigate();return (<div><p>登录页</p><button onClick={() => navigate("/register?phone=138&email=example@site.com")}>前往注册页</button></div>);
      };export default Login;
    • src\page\Register\index.js

      import { useSearchParams } from "react-router-dom";const Register = () => {const [params] = useSearchParams();let phone = params.get("phone");let email = params.get("email");return (<div><p>注册页</p><p>手机: {phone}</p><p>邮箱: {email}</p></div>);
      };export default Register;
  2. 基于编程式导航,使用 useParams 钩子获取 URI 中的动态路由参数,如:

    • src\router\index.js

      // ...
      {path: "/register/:phone/:email",element: <Register />,
      },
      // ...
      
    • src\page\Login\index.js

      import { useNavigate } from "react-router-dom";const Login = () => {const navigate = useNavigate();return (<div><p>登录页</p><button onClick={() => navigate("/register/138123456/example@site.com")}>前往注册页</button></div>);
      };export default Login;
    • src\page\Register\index.js

      import { useParams } from "react-router-dom";const Register = () => {const params = useParams();let phone = params.phone;let email = params.email;return (<div><p>注册页</p><p>手机: {phone}</p><p>邮箱: {email}</p></div>);
      };export default Register;

(5)嵌套路由

  • 嵌套路由是指在一级路由下内嵌其他路由(二级路由)

  • 举例:

    • 创建 src\page\About\index.js

      const About = () => {return <div>关于页</div>;
      };export default About;
    • 创建 src\page\Blog\index.js

      const Blog = () => {return <div>博客页</div>;
      };export default Blog;
    • 创建 src\page\Home\index.js

      import { Link, Outlet } from "react-router-dom";const Home = () => {return (<div><p>主页</p><Link to="about">关于</Link><br /><Link to="blog">博客</Link><divstyle={{border: "2px solid black",}}><Outlet /></div></div>);
      };export default Home;
    • 修改 src\router\index.js,配置嵌套路由

      import { createBrowserRouter } from "react-router-dom";
      import Home from "../page/Home";
      import About from "../page/About";
      import Blog from "../page/Blog";const router = createBrowserRouter([{path: "/home",element: <Home />,children: [{path: "about",element: <About />},{path: "blog",element: <Blog />}]},
      ]);export default router;
  • 默认二级路由:当一级路由显示时需要某个指定二级路由同时显示时,需要设置默认二级路由

    • 修改 src\router\index.js,配置默认二级路由

      // ...
      children: [{index: true,element: <About />},{path: "blog",element: <Blog />}
      ]
      // ...
      
    • 修改 src\page\Home\index.js

      {/* ... */}
      <Link to="/home">关于</Link>
      <br />
      <Link to="blog">博客</Link>
      {/* ... */}
      

(6)通配路由

  • 所谓通配路由其实常用于,当访问的路由不存在时,弹出的 404 页面

  • 举例:

    • 创建 src\page\NotFound\index.js

      const NotFound = () => {return <div>404 Not Found</div>;
      };export default NotFound;
    • 修改 src\router\index.js,配置通配路由

      import { createBrowserRouter } from "react-router-dom";
      import NotFound from "../page/NotFound";const router = createBrowserRouter([{path: "*",element: <NotFound />,},
      ]);export default router;

(7)路由模式

  • 路由模式主要包括 hash 模式和 history 模式,两者相比:

    路由模式 URL 原理 是否需要后端支持 创建方法
    hash url/#/route 监听 hashChange 事件 createHashRouter
    history url/route history 对象 + pushState 事件 createBrowerRouter
  • 路由模式的选择在 src\router\index.js 中实现,如:

    import { createBrowserRouter, createHashRouter } from "react-router-dom";const routerHash = createHashRouter();
    const routerHistory = createBrowserRouter();export { routerHash, routerHistory };

0x04 React 高级

(1)React.memo

  • React.memo 用于允许组件在 Props 没有改变的情况下跳过渲染

    • React 默认渲染机制:当父组件重新渲染,则子组件也重新渲染

    • 使用 memo 函数包裹生成的缓存组件只有在 props 发生改变时重新渲染

      const MemoComp = memo(function CusComp(props) {})
      
  • 举例:

    import { memo, useState } from "react";const Comp = () => {console.log("子组件重新渲染");return <div>Comp</div>;
    };const MemoComp = memo(function Comp(props) {console.log("缓存子组件重新渲染");return <div>MemoComp</div>;
    });function App() {const [count, setCount] = useState(0);return (<div><button onClick={() => setCount(count + 1)}>change</button><Comp /><MemoComp /></div>);
    }export default App;
  • 在使用 memo 缓存组件后,React 会对每个 prop 使用 Object.is 比较,true 为没有变化,false 为有变化

    • 当 prop 是简单类型(如数字、字符串等),Object.is(3, 3) 返回 true,即没有变化
    • 当 prop 是引用类型(如数组、对象等),Object.is([], []) 返回 false,即有变化,因为引用发生变化
    import { memo, useMemo, useState } from "react";const AComp = memo(function Comp({ prop }) {console.log("A 组件重新渲染");return <div>AComp: {prop}</div>;
    });const BComp = memo(function Comp({ prop }) {console.log("B 组件重新渲染");return <div>BComp: {prop}</div>;
    });function App() {const [count, setCount] = useState(0);const list = useMemo(() => {return [1, 2, 3];}, []);return (<div><button onClick={() => setCount(count + 1)}>change</button><AComp prop={count} /><BComp prop={list} /></div>);
    }export default App;

(2)React.forwardRef

  • React.forwardRef 用于通过使用 ref 暴露 DOM 节点给父组件

  • 举例:

    import { forwardRef, useRef } from "react";const Comp = forwardRef((props, ref) => {return <input type="text" ref={ref} autoFocus />;
    });function App() {const compRef = useRef(null);const handleClick = () => {console.log(compRef.current.value);};return (<div><Comp ref={compRef} /><button onClick={handleClick}>Click</button></div>);
    }export default App;

(3)useInperativeHandle

  • useInperativeHandle 钩子用于通过 ref 暴露子组件的方法给父组件使用

  • 举例:

    import { forwardRef, useImperativeHandle, useRef } from "react";const Comp = forwardRef((props, ref) => {const compRef = useRef(null);const focusHandle = () => {compRef.current.focus();};useImperativeHandle(ref, () => {return {focusHandle,};});return <input type="text" ref={compRef} />;
    });function App() {const compRef = useRef(null);const handleClick = () => {compRef.current.focusHandle();};return (<div><Comp ref={compRef} /><button onClick={handleClick}>Click</button></div>);
    }export default App;

(4)常用第三方包

a. SASS/SCSS

  • SCSS 是一种预编译 CSS 语言,其文件后缀名为 .scss,支持一些原生 CSS 不支持的高级用法,如变量使用、嵌套语法等

  • 使用命令 npm install sass -D 安装 SASS,-D 表示仅在开发模式使用,不会打包到生产模式

  • 使用 SCSS:

    1. 修改 src/index.css 为 src/index.scss

      body {div {color: red;}
      }
    2. 修改 src/index.js

      // ...
      import "./index.scss"
      // ...
      
    3. 修改 src/App.js

      function App() {return <div>文本内容</div>;
      }export default App;

b. Ant Design

  • Ant Design(简称 AntD)是 React PC 端组件库,由蚂蚁金服出品,内置常用组件

  • 使用命令 npm i antd --save 安装 AntD,--save 表示将模块添加到配置文件中的运行依赖中

  • 使用 AntD:修改 src/App.js

    import { Button } from "antd";function App() {return <Button type="primary">Button</Button>;
    }export default App;

c. Zustand

  • Zustand 用于状态管理

  • 使用命令 npm i zustand 安装 Zustand

  • 使用 Zustand:修改 src/App.js

    import { create } from "zustand";const store = create((set) => {return {count: 0,increment: () => set((state) => ({ count: state.count + 1 })),decrement: () => set((state) => ({ count: state.count - 1 })),};
    });function App() {const { count, increment, decrement } = store();return (<div><button onClick={decrement}>-1</button><span>{count}</span><button onClick={increment}>+1</button></div>);
    }export default App;
  • 在异步方面,Zustand 支持直接在函数中编写异步逻辑

    const store = create((set) => {return {channelList: [],fetchChannelList: async () => {const URL = "";const res = await axios.get(URL);const data = await res.json();set({channelList: data.data.channelList,});},};
    });
    
  • 当单个 store 较大时,可以通过切片模式进行模块拆分组合,即模块化

    const createAStore = create((set) => {return {};
    });const createBStore = create((set) => {return {};
    });const store = create((...a) => ({...createAStore(...a),...createBStore(...a),
    }));
    

(5)类组件

a. 概述

  • 类组件是通过 JavaScript 中的类来组织组件的代码

    1. 通过属性 state 定义状态数据
    2. 通过方法 setState 修改状态数据
    3. 通过方法 render 渲染 JSX
  • 举例:

    import { Component } from "react";class Counter extends Component {constructor(props) {super(props);this.state = {count: 0,};}increment = () => {this.setState({ count: this.state.count + 1 });};decrement = () => {this.setState({ count: this.state.count - 1 });};render() {return (<div><button onClick={this.decrement}>-1</button><span>{this.state.count}</span><button onClick={this.increment}>+1</button></div>);}
    }function App() {return <Counter />;
    }export default App;

b. 生命周期

  • 生命周期指组件从创建到销毁的各个阶段,这些阶段自动执行的函数称为生命周期函数

    https://ask.qcloudimg.com/http-save/yehe-10021778/0e399bb140db5e51ef0ef635a6b06747.png
  • 常用生命周期函数:

    • componentDidMount:组件挂载完成后执行,常用于异步数据获取
    • componentWillUnmount:组件卸载时执行,常用于清理副作用方法
  • 举例:

    import { Component, useState } from "react";class Child extends Component {componentDidMount() {console.log("组件挂载完成");}componentWillUnmount() {console.log("组件即将卸载");}render() {return <div>子组件</div>;}
    }function App() {const [show, setShow] = useState(true);return (<div>{show && <Child />}<button onClick={() => setShow(!show)}>{show ? "隐藏" : "显示"}子组件</button></div>);
    }export default App;

c. 组件通信

  • 方法与组件通信类似

    • 父传子:通过 prop 绑定数据
    • 子传父:通过 prop 绑定父组件方法
    • 兄弟间:状态提示,通过父组件做状态桥接
  • 举例:

    import { Component } from "react";class AChild extends Component {render() {return <div>子组件 A: {this.props.data}</div>;}
    }class BChild extends Component {render() {return (<div><p>子组件 B</p><button onClick={() => this.props.onGetData(456)}>发送</button></div>);}
    }class Parent extends Component {state = {data: 123,};getData = (data) => {console.log(data);};render() {return (<div><p>父组件</p><AChild data={this.state.data} /><BChild onGetData={this.getData} /></div>);}
    }function App() {return (<div><Parent /></div>);
    }export default App;

0x05 结合 TypeScript

(1)创建开发环境

  1. 使用命令 npm create vite@latest react-ts-app -- --template react-ts 创建使用 TypeScript 的 React 工程

    • 首次执行该命令时,需要同意安装 Vite,选择 React 框架,选择 TypeScript
  2. 使用命令 cd react-ts-app 进入工程目录

  3. 使用命令 npm install 安装必要依赖

  4. 使用命令 npm run dev 启动工程

  5. 工程目录中,代码资源文件在 src 目录下,其中:

    • App.tsx:应用文件

      function App() {return <>App</>;
      }export default App;
    • main.tsx:入口文件

      import ReactDOM from "react-dom/client";
      import App from "./App.tsx";ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
    • vite-env.d.ts:环境配置文件

      /// <reference types="vite/client" />

(2)useState

  • React 会根据传入 useState 的默认值来自动推导数据类型,无需显式标注

    import { useState } from "react";function App() {const [value, setValue] = useState(0);const change = () => {setValue(100);};return (<>{value}<button onClick={() => change()}>Click</button></>);
    }export default App;
  • useState 本身是泛型函数,可以传入自定义类型

    type User = {name: string;age: number;
    };const [user, setUser] = useState<User>();
    
    • 限制 useState 函数参数的初始值必须满足类型 User | () => User
    • 限制 useState 函数的参数必须满足类型 User | () => User | undefined
    • 状态数据 user 具备 User 类型相关类型提示
  • 当不确定初始值应该为什么类型时,将 useState 的初始值设为 null,如:

    type User = {name: string;age: number;
    };const [user, setUser] = useState<User | null>(null);
    

(3)Props

  • Props 添加类型是在给函数的参数做类型注解,可以使用 type 对象类型或 interface 接口,如:

    type Props = {className: string;style: object;onGetData?: (data: number) => void;
    };function Comp(props: Props) {const { className, style, onGetData } = props;const handleClick = () => {onGetData?.(123);};return (<div><div className={className} style={style}>子组件文本内容</div><button onClick={handleClick}>发送 123</button></div>);
    }function App() {const getData = (data: number) => {console.log(data);};return (<><Comp className="foo" style={{ color: "red" }} onGetData={getData} /></>);
    }export default App;
  • children 是一个比较特殊的 prop,支持多种不同类型数据的输入

    type Props = {children: React.ReactNode;
    };function Comp(props: Props) {const { children } = props;return <div>{children}</div>;
    }function App() {return (<><Comp><button>Click</button></Comp></>);
    }export default App;

(4)useRef

  • 可以直接把需要获取的 DOM 元素的类型,作为泛型参数传递给 useRef

    import { useEffect, useRef } from "react";function App() {const inputRef = useRef<HTMLInputElement>(null);useEffect(() => {inputRef.current?.focus();}, []);return (<><input ref={inputRef} /></>);
    }export default App;

-End-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/47308.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

7-Zip

最牛掰的解压缩的开源软件!赏!!!!概述 7-Zip 是一款免费开源的文件归档和压缩软件,由于其高压缩比和支持多种格式等优势,越来越受到用户的青睐。7-Zip 最初由一位俄罗斯程序员开发,现已经成为全球领先的压缩软件之一。 作为开源免费的 7-Zip 解压缩工具,除了常见的压缩…

FQC外检机使用Profibus转Modbus网关提升工作效率

本文介绍了PLC通过Profibus转Modbus网关(XD-MDPB100)与视觉传感器实现通讯,在FQC外检机中提升自动化和检测效率。控制器通过Profibus转Modbus网关(XD-MDPB100)与视觉传感器实现通讯,在FQC外检机的应用为生产流程的自动化和优化提供了重要支持。在工业自动化领域,PLC常被…

Flutter 借助SearchDelegate实现搜索页面,实现搜索建议、搜索结果,解决IOS拼音问题

使用Flutter自带的SearchDelegate组件实现搜索界面,通过魔改实现如下效果:搜素建议、搜索结果,支持刷新和加载更多,解决IOS中文输入拼音问题。搜索界面使用Flutter自带的SearchDelegate组件实现,通过魔改实现如下效果:搜素建议 搜索结果,支持刷新和加载更多 IOS中文输入…

.NET Core 3.x 基于AspectCore实现AOP,实现事务、缓存拦截器

最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理。给一个方法加一个缓存特性,那这个方法就会进行缓存。这个也是网上说的面向切面编程AOP。AOP的概念也很好理解,跟中间件差不多,说白了,就是我可以任意地在方法的前面…

内存调优实战

实战篇1、内存调优1.1 内存溢出和内存泄漏内存泄漏(memory leak):在Java中如果不再使用一个对象,但是该对象依然在GC ROOT的引用链上,这个对象就不会被垃圾回收器回收,这种情况就称之为内存泄漏。内存泄漏绝大多数情况都是由堆内存泄漏引起的,所以后续没有特别说明则讨论…

团队开发日记4

日期:2024年5月11日标题:校园兼职招聘系统开发日记 - 收藏功能和邮箱功能实现 项目概述:今天我们完成了用户收藏功能和邮箱功能的设计和实现,增强了系统的个性化和通知能力。 当天的工作内容:李健龙负责了用户收藏兼职和帖子的页面设计和数据存储逻辑。 郑盾实现了系统的邮…

团队开发日记2

日期:2024年5月5日标题:校园兼职招聘系统开发日记 - 兼职发布与搜索功能实现 项目概述:今天我们重点完成了兼职发布和搜索功能的初步实现,用户可以发布兼职信息并进行基本的搜索。 当天的工作内容:李健龙完成了兼职信息录入页面的前端设计和部分后端逻辑的编写。 郑盾实现…

[模式识别复习笔记] 第4章 SVM

1. SVM 简介 1.1 SVM 支持向量机 给定如图所示的线性可分训练集,能够将两类样本正确分开的直线很多。 感知机算法可以找到一条直线,且找到的直线不唯一。 然而感知机无法确定哪一条直线最优,但是 \(\text{SVM}\) 可以。 \(\text{SVM}\) 可以找到能够将训练样本正确分类的直线…