react基础知识(下)

一些适用的知识点

可执行命令

start:开发时启动sum的命令

build:开发完毕后打包的命令

classnames

npm install classnames

js库

js 复制代码
静态类名和动态类名
className = {classNames('nav-item',{active:type===item.type})}

受控表单绑定

js 复制代码
const [value,setValue] = useState('')
js 复制代码
<input
	value={value}
	onChange = {(e)=>setValue(e.target.value)}
/>

useRef获取dom

在react组件中获取/操作DOM,需要使用useRef钩子函数,分为两步

  1. 使用useRef创建ref对象,并与JSX绑定

    js 复制代码
    const inputRef = useRef(null)
    <input type="text" ref={inputRef} />
  2. 在DOM可用时,通过inputRef.current拿到DOM对象

    js 复制代码
    console.log(inputRef.current)

使用的 lodash库

  1. 生成唯一的随机数---uuid
  2. 以当前时间为标准,生成固定格式---dayjs

组件通信

父子通信

父传子

  1. props可传递任意的数据:数字、字符串、数组、对象、函数、JSX

  2. props是只读对象,子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改

  3. props.children
    当我们在使用子组件标签时,在标签内部写入的内容,React 会自动将其作为一个特殊的 prop,即 props.children,传递给子组件。子组件可以通过访问这个属性来获取并渲染父组件传递过来的内容。

    js 复制代码
    import React from'react';
    import Son from './Son';
    
    function Parent() {
      return (
        <Son>
          <span>this is span</span>
        </Son>
      );
    }
    
    // 子组件
    function Son({ children }) {
      return (
        <div>
          <h2>子组件接收到的内容:</h2>
          {children}
        </div>
      );
    }
    
    export default Parent;

当传递多个元素时,props.children 是一个数组,包含了 <span> 和 <p> 这两个元素,子组件需要按照数组的方式去处理和渲染这些元素。

js 复制代码
function Parent() {
  return (
    <Son>
      <span>this is span</span>
      <p>this is a paragraph</p>
    </Son>
  );
}

子传父

子组件通过调用父组件传递过来的回调函数,将数据传递给父组件。

js 复制代码
// 父组件
function Parent() {
  const handleChildData = (data) => {
    console.log('接收到子组件的数据:', data);
  };
  return (
    <Son sendDataToParent={handleChildData} />
  );
}

// 子组件
function Son({ sendDataToParent }) {
  const childData = "子组件的数据";
  const handleClick = () => {
    sendDataToParent(childData);
  };
  return (
    <div>
      <button onClick={handleClick}>发送数据给父组件</button>
    </div>
  );
}

兄弟通信

通常需要借助它们共同的父组件来实现。兄弟组件 A 可以通过调用父组件的函数,将数据传递给父组件,然后父组件再将数据传递给兄弟组件 B

js 复制代码
import React, { useState } from'react';

// 兄弟组件A
function BrotherA({ sendDataToParent }) {
  const data = "这是来自BrotherA的数据";
  const handleSend = () => {
    sendDataToParent(data);
  };
  return (
    <div>
      <button onClick={handleSend}>发送数据给兄弟组件</button>
    </div>
  );
}

// 兄弟组件B
function BrotherB({ receivedData }) {
  return (
    <div>
      <p>接收到的兄弟组件数据: {receivedData}</p>
    </div>
  );
}

// 父组件
function Parent() {
  const [dataFromBrotherA, setDataFromBrotherA] = useState('');
  const handleReceiveFromA = (data) => {
    setDataFromBrotherA(data);
  };
  return (
    <div>
      <h2>兄弟组件通信示例 - 通过父组件中转</h2>
      <BrotherA sendDataToParent={handleReceiveFromA} />
      <BrotherB receivedData={dataFromBrotherA} />
    </div>
  );
}

export default Parent;

跨层通信

使用 Context API:适用于需要在多层嵌套组件之间共享数据,而不需要逐层传递 props 的场景

js 复制代码
// 创建 Context
const MyContext = React.createContext();

// 提供者组件
function Provider({ children }) {
  const value = "共享的数据";
  return (
    <MyContext.Provider value={value}>
      {children}
    </MyContext.Provider>
  );
}

// 消费者组件
function Consumer() {
  return (
    <MyContext.Consumer>
      {value => <p>{value}</p>}
    </MyContext.Consumer>
  );
}

function App() {
  return (
    <Provider>
      <Consumer />  这里使用的props.children插槽写法
    </Provider>
  );
}

也可以借助redux等状态管理工具,下面该状态管理工具的详细介绍

useEffect

useEffect 是一个非常重要的 Hook,用于处理副作用操作,比如数据获取、订阅、手动更改 DOM 等。它可以让函数组件拥有类似类组件中生命周期钩子的能力。

js 复制代码
import React, { useEffect } from'react';

function MyComponent() {
  useEffect(() => {
    // 在这里编写副作用代码,比如数据获取、订阅等
    console.log('副作用操作执行');

    return () => {
      // 在这里进行清理工作,比如取消订阅、清除定时器等
      console.log('清理操作执行');
    };
  }, []); // 第二个参数是依赖数组

  return (
    <div>
      {/* 组件的JSX内容 */}
    </div>
  );
}

useEffect依赖项

清除副作用

js 复制代码
import React, { useEffect, useState } from'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);

    return () => {
      clearInterval(intervalId); // 组件卸载时清除定时器
    };
  }, []);

  return (
    <div>
      <p>已过去的秒数: {seconds}</p>
    </div>
  );
}

useEffect的返回函数会在组件卸载时执行,或者在下一次useEffect运行之前执行(如果useEffect有依赖项的话)。这个返回函数通常用于清理在useEffect中设置的任何副作用,如定时器、事件监听器或订阅。

为什么 async 不能写在 useEffect 的回调函数前面?

1.回调函数设计与返回值用途
  • React :useEffect 的回调函数设计比较特殊,它允许返回一个清理函数,用于在组件卸载或者依赖项变化时清除副作用,比如取消订阅、清除定时器等。由于 async 函数会隐式返回一个 Promise,这就和 useEffect 期望的返回值(清理函数或者 undefined )产生了冲突,所以不能直接用 async 修饰 useEffect 的回调函数。
  • Vue:onMounted 等钩子函数并没有这样对返回值的特殊要求,开发者可以根据需要自由编写异步代码,不会影响 Vue 对组件生命周期的管理。
2. 异步处理和状态管理的方式
  • React :React 自身并没有内置的像 Vue 那样强大且统一的响应式系统,在处理异步操作后的状态更新时,需要开发者手动去管理状态的更新和组件的重新渲染,并且要特别注意避免因为异步操作导致的一些问题,比如竞态条件、闭包陷阱等,这就使得在 useEffect 中对异步操作的处理需要遵循特定的规则。
  • Vue:Vue 的响应式系统会自动追踪依赖并在数据变化时更新视图,在 onMounted 中使用 async 进行异步数据获取并更新响应式数据,Vue 能够很方便地处理视图的更新,不需要开发者像在 React 中那样进行复杂的手动处理。

如何在 useEffect 中使用异步操作?

虽然 useEffect 的回调函数不能直接声明为 async,但你可以在回调函数内部定义一个 async 函数,并立即调用它。这样可以实现异步操作

js 复制代码
useEffect(() => {
  // 定义异步函数
  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data');
      const json = await response.json();
      setData(json);
    } catch (error) {
      console.error('Error fetching data:', error);
    }
  };

  fetchData(); // 立即调用异步函数

  // 返回清理函数(可选)
  return () => {
    // 组件卸载时的清理逻辑
  };
}, []); // 依赖数组为空,仅在挂载时执行一次
  • 使用立即执行的异步函数表达式
js 复制代码
useEffect(() => {
  (async () => {
    try {
      const data = await fetchData();
      setData(data);
    } catch (error) {
      console.error(error);
    }
  })(); // 立即执行异步函数

  return () => { /* 清理逻辑 */ };
}, []);
  • 使用 Promise 链式调用
js 复制代码
useEffect(() => {
 fetch('https://api.example.com/data')
   .then(response => response.json())
   .then(json => setData(json))
   .catch(error => console.error(error));

 return () => { /* 清理逻辑 */ };
}, []);

处理异步操作中的清理需求

当异步操作需要在组件卸载前取消时(如取消未完成的请求),可以使用 AbortController:

js 复制代码
useEffect(() => {
  const controller = new AbortController();

  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data', {
        signal: controller.signal // 关联 AbortController
      });
      const json = await response.json();
      setData(json);
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('请求被取消');
      } else {
        console.error('Error fetching data:', error);
      }
    }
  };

  fetchData();

  // 组件卸载时取消请求
  return () => {
    controller.abort(); // 取消未完成的请求
  };
}, []);

自定义hook

自定义 Hook(Custom Hook) 是一种复用有状态逻辑的方式,它允许你将可复用的逻辑提取到独立的函数中,同时保留状态管理和副作用的能力。

什么是自定义 Hook

自定义hook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用

  • 复用逻辑:将有状态的逻辑(如数据获取、表单验证、事件监听)封装成可复用的函数。
  • 保留状态:每次调用 Hook 时,其内部状态都是独立的。
  • 遵循 Hook 规则:只能在函数组件或其他 Hook 中调用,且不要在循环 / 条件语句中调用。

为什么需要自定义 Hook?

  1. 解决代码重复问题
    当多个组件需要实现相同的逻辑(如获取用户位置、处理表单验证)时,自定义 Hook 可以避免代码冗余。
  2. 分离关注点
    将复杂逻辑从组件中提取出来,使组件更专注于 UI 渲染,提高代码可读性和可维护性。
  3. 状态逻辑复用
    与普通函数不同,自定义 Hook 可以使用 React 的状态和副作用管理,保留状态的独立性。

封装自定义函数

js 复制代码
// useCounter.js
import { useState, useCallback } from'react';

export function useCounter(initialValue = 0) {
  // 使用 useState 管理状态
  const [count, setCount] = useState(initialValue);

  // 使用 useCallback 优化回调函数
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);

  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);

  // 返回状态和操作函数
  return {
    count,
    increment,
    decrement,
    reset
  };
}

使用自定义函数

js 复制代码
import { useCounter } from './useCounter';

function CounterComponent() {
  // 使用自定义Hook
  const { count, increment, decrement, reset } = useCounter(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Hook 规则

  • 只在顶层调用 Hook:不要在循环、条件语句或嵌套函数中调用 Hook,确保 Hook 调用顺序一致。
  • 只在 React 函数中调用 Hook:不要在普通 JavaScript 函数中调用 Hook。
  • 每个组件调用 Hook 时,状态是独立的。如果需要共享状态,可以使用 Context 或状态管理库

Redux

原始代码

js 复制代码
使用 CRA 快速创建 React 项目
npx create - react - app react - redux
安装配套工具
npm i @reduxjs/toolkit react - redux
启动项目
npm run start
js 复制代码
<script>
// 1. 定义reducer函数
// 作用: 根据不同的action对象, 返回不同的新的state
// state: 管理的数据初始状态
// action: 对象 type 标记当前想要做什么样的修改
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
}

// 2. 使用reducer函数生成store实例
const store = Redux.createStore(reducer)

// 3. 通过store实例的subscribe订阅数据变化
// 回调函数可以在每次state发生变化的时候自动执行
store.subscribe(() => {
  console.log('state变化了')
})

// 4. 通过store实例的dispatch函数提交action更改状态
const inBtn = document.getElementById('increment')
inBtn.addEventListener('click', () => {
  // 增
  store.dispatch({
    type: 'INCREMENT'
  })
})

const dBtn = document.getElementById('decrement')
dBtn.addEventListener('click', () => {
  // 减
  store.dispatch({
    type: 'DECREMENT'
  })
})
</script>

为什么要使用dispatch

  1. dispatch 是触发状态变更的唯一入口
  • 在 Redux 中,状态(state)是只读的,不能直接修改(如 state.count++ 是被禁止的)。
  • 必须通过 dispatch 发送一个 action ,才能触发 reducer 计算新状态。
    类比:就像你想让手机拍照,必须按下快门键(dispatch),而不是直接修改相册文件(直接改状态)。没有快门键(dispatch),你无法告诉手机 "我要拍照"(触发动作)。
  1. dispatch 是连接视图层和状态层的桥梁
  • 视图层(如按钮点击事件)无法直接访问或修改 reducer 和 store,必须通过 dispatch 发送 action 来 "告知" 状态层需要做什么。
javascript 复制代码
// 没有 dispatch,点击事件无法触发状态变更
inBtn.addEventListener('click', () => {
  // ❌ 非法操作:直接修改 state(Redux 不允许)
  store.state.count += 1; 
});
  • 合法操作:通过 dispatch 发送 action,由 reducer 处理状态变更:
javascript 复制代码
inBtn.addEventListener('click', () => {
  store.dispatch({ type: 'INCREMENT' }); // ✅ 正确方式
});
  1. dispatch 是数据流可追踪的基础
  • 所有状态变更都必须经过 dispatch,因此:
    • 可以通过 Redux DevTools 追踪每一次 dispatch 的 action,查看状态变化的历史记录(时间旅行调试)。
    • 可以通过中间件(如 Redux Thunk)在 dispatch 过程中拦截、处理异步逻辑(如网络请求)。
  • 如果没有 dispatch,状态变更可能来自任意地方(如视图层直接修改状态),导致数据流混乱,无法追踪和调试。

使用配套工具

js 复制代码
npx create-react-app react-redux-demo
cd react-redux-demo
npm i @reduxjs/toolkit react-redux


1.创建 store 目录及相关文件

在项目根目录下创建 store 目录,然后在 store 目录下创建 modules 目录,接着在 store/modules 目录下创建 counterStore.js 文件,内容如下:

js 复制代码
import { createSlice } from '@reduxjs/toolkit';

// 创建一个 slice,它包含了状态、reducer 和 action creators
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 }, // 初始状态,计数器初始值为 0
  reducers: {
    increment: (state) => {
      state.value++; // 使用 @reduxjs/toolkit 可以直接修改状态,内部会处理不可变更新
    },
    decrement: (state) => {
      state.value--;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

// 导出 action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出 reducer
export default counterSlice.reducer;
  1. 在 store 目录下创建 index.js 文件,用于组合模块并导出 store:
js 复制代码
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './modules/counterStore';

const store = configureStore({
  reducer: {
    counter: counterReducer, // 将 counterReducer 挂载到 store 的 counter 键下
  },
});

export default store;
  1. 在 React 组件中使用 Redux
js 复制代码
import React from'react';
import { useSelector, useDispatch } from'react-redux';
import { increment, decrement, incrementByAmount } from './store/modules/counterStore';
import store from './store';

function App() {
  // 使用 useSelector 从 store 中获取状态
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch(); // 获取 dispatch 函数

  return (
    <div className="App">
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <input
        type="number"
        placeholder="Increment by amount"
        onChange={(e) => {
          const amount = parseInt(e.target.value, 10);
          if (!isNaN(amount)) {
            dispatch(incrementByAmount(amount));
          }
        }}
      />
    </div>
  );
}

export default App;

还有第二种写法

js 复制代码
 const {count} = useSelector((state) => state.counter);

action传参

  1. 定义 reducer
js 复制代码
// 引入 createSlice 用于创建 Redux slice
import { createSlice } from '@reduxjs/toolkit';

// 创建计数器 slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    // 传入参数增加计数器值的 action
    incrementByAmount: (state, action) => {
      // 从 action.payload 获取传递的参数值
      state.value += action.payload; 
    }
  }
});

// 导出 action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出 reducer
export default counterSlice.reducer;

这里 incrementByAmount 是一个接收参数的 action 处理函数,它从 action.payload 读取外部传入的值来更新计数器状态。
当使用 @reduxjs/toolkit 的 createSlice 定义 reducers 时,action.payload 会被自动解析为传入的参数。例如:

  1. 在组件中使用并传参
js 复制代码
import React from'react';
import { useSelector, useDispatch } from'react-redux';
// 导入前面定义的 action creators
import { increment, decrement, incrementByAmount } from './path/to/counterSlice';

function App() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <input
        type="number"
        placeholder="Increment by amount"
        onChange={(e) => {
          const amount = parseInt(e.target.value, 10);
          if (!isNaN(amount)) {
            // 调用 incrementByAmount 并传递输入的数值作为参数
            dispatch(incrementByAmount(amount)); 
          }
        }}
      />
    </div>
  );
}

export default App;

用户在输入框输入数字,点击相关按钮时,incrementByAmount action 被触发,输入的数字作为 action.payload 传递给 reducer ,从而实现按指定数值增加计数器的功能。

异步action

  1. 定义 reducer 和相关 action
javascript 复制代码
import { createSlice } from '@reduxjs/toolkit';
// 假设使用 axios 进行网络请求,先引入
import axios from 'axios';

const userSlice = createSlice({
  name: 'users',
  initialState: {
    list: [],
    isLoading: false,
    error: null
  },
  reducers: {
  }
});

// 定义异步 action creator
const fetchUsers = () => {
  return async (dispatch) => {
    dispatch(setLoading());
    try {
      // 模拟网络请求获取用户列表数据
      const response = await axios.get('/api/users'); 
      dispatch(setUsers(response.data));
    } catch (error) {
      dispatch(setError(error.message));
    }
  };
};

export { fetchUsers };
export default userSlice.reducer;

为什么异步 action 是放在外面的

  • 代码组织与可维护性:把异步 action 相关逻辑集中定义,与同步 action 和 reducer 放在一起,使整个状态管理逻辑代码结构清晰。比如在一个管理用户相关状态的文件里,同步的用户状态更新(如设置用户信息 )和异步的获取用户列表操作都能集中呈现,后续维护和查找功能逻辑更方便。

  • 便于复用与测试:集中定义的异步 action 可以在多个组件中方便地复用。同时,在进行单元测试时,能更方便地针对异步 action 的逻辑进行测试,不需要在各个使用它的组件中分散测试相关逻辑。

为什么 dispatch 可以作为参数传递

  1. Redux-Thunk 中间件的作用
  • 上述异步函数(称为 Thunk 函数)能接收 dispatch 作为参数,依赖于 Redux-Thunk 中间件的机制:

当组件调用 dispatch(thunkFunction) 时,Redux-Thunk 会拦截这个 thunkFunction,并主动将 dispatch 和 getState(store 的状态)作为参数传入。

  • 因此,Thunk 函数的参数 dispatch 实际上是 store 提供的真实 dispatch 函数,可以直接用于触发 action。
  1. 函数参数的本质

    从 JavaScript 函数的角度看:

    • dispatch 是一个函数引用,可以像普通变量一样作为参数传递给其他函数。
    • 异步函数接收 dispatch 作为参数,本质上是接收了一个 "触发状态更新的能力",与函数接收其他回调函数(如 onSuccess)的逻辑一致。
  2. 在组件中调用异步 action

js 复制代码
import React from'react';
import { useSelector, useDispatch } from'react-redux';
// 导入异步 action
import { fetchUsers } from './path/to/userSlice';

function UserList() {
 
  React.useEffect(() => {
    // 组件挂载时触发获取用户列表的异步操作
    dispatch(fetchUsers()); 
  }, [dispatch]);

  return (
    <div>
     
    </div>
  );
}

export default UserList;

在 UserList 组件中,通过 useEffect 在组件挂载时调用 dispatch(fetchUsers()) 触发异步操作。根据不同的状态(加载中、成功、失败 ),组件会显示相应的内容,实现了异步获取数据并更新 UI 的功能。

为什么依赖项是dispatch

  • 由于 React 函数组件重新渲染会重建作用域,如果存在一些特殊情况(比如在某些复杂的高阶组件封装、或者不规范的状态管理逻辑下 ),可能导致 useDispatch 每次返回的 dispatch 函数引用发生变化。

  • 这个 useEffect 内部的逻辑依赖于 dispatch 函数,如果 dispatch 函数的引用发生了变化(即它本身变了 ),就需要重新执行 useEffect 里的代码"。这样即使在一些极端或意外情况下 dispatch 函数引用改变了,也能保证 useEffect 内触发异步操作(dispatch(fetchUsers()) )的逻辑能正常运行,避免因使用了旧的 dispatch 函数引用而导致异步操作无法正确触发的问题。 虽然在常规、规范的代码中 dispatch 函数引用通常是稳定不变的,但遵循这样的处理方式是一种更严谨、符合最佳实践的做法。

Router

js 复制代码
npm i react-router-dom

核心组件


常用钩子

路由跳转

  • 使用 组件 *:这是最常用的方式,类似于 HTML 中的 <a> 标签,但在 React Router 中使用它进行导航时不会重新加载整个页面,而是在单页应用内进行跳转。例如:
js 复制代码
import { Link } from'react-router-dom';
function Navbar() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
    </nav>
  );
}
  • 编程式导航:通过 useNavigate 钩子函数来实现路由跳转
js 复制代码
import { useNavigate } from'react-router-dom';
function Login() {
  const navigate = useNavigate();
  const handleSubmit = () => {
    // 假设登录成功
    navigate('/dashboard');
  };
  return (
    <form onSubmit={handleSubmit}>
      {/* 登录表单内容 */}
    </form>
  );
}

跳转传参

  • params 传参:通过在路由路径中定义参数来传递数据。例如,定义一个用户详情页路由 /user/:id ,其中 :id 就是参数。在组件中可以通过 useParams 钩子获取参数值:
js 复制代码
// 路由配置
<Route path="/user/:id" element={<UserDetail />} />

// UserDetail 组件
import { useParams } from'react-router-dom';
function UserDetail() {
  const { id } = useParams();
  return <div>用户 ID:{id}</div>;
}
  • 跳转时可以这样写:
js 复制代码
import { Link } from'react-router-dom';
function UserList({ users }) {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          <Link to={`/user/${user.id}`}>{user.name}</Link>
        </li>
      ))}
    </ul>
  );
}
  • 查询参数传参:通过在 URL 中添加查询字符串来传递参数,如 /search?keyword=react 。可以使用 useSearchParams 钩子来获取查询参数:
js 复制代码
import { useSearchParams } from'react-router-dom';
function SearchResults() {
  const [searchParams] = useSearchParams();
  const keyword = searchParams.get('keyword');
  return <div>搜索关键词:{keyword}</div>;
}
  • 跳转时:
js 复制代码
import { useNavigate } from'react-router-dom';
function SearchForm() {
  const navigate = useNavigate();
  const handleSubmit = (e) => {
    e.preventDefault();
    const keyword = e.target.elements.keyword.value;
    navigate(`/search?keyword=${keyword}`);
  };
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="keyword" />
      <button type="submit">搜索</button>
    </form>
  );
}

获取当前路径

js 复制代码
  import { useLocation } from 'react-router-dom'

const About = () => {
  // 使用 hook
  const location = useLocation();
  const { from, pathname } = location

  return <div>这里是卡拉云的网站,你当前在 {pathname},你是从 {from} 跳转过来的</div>
}

location和useSearchParams区别

点击链接查看和 Kimi 的对话 https://kimi.moonshot.cn/share/d0dkul3ua3d2tdsjh830

404页面

我们只要在最后加入 path 为* 的一个路径,意为匹配所有路径,即可

js 复制代码
function App() {

  return <BrowserRouter>
    <Routes>
      <Route path="*" element={<NotFound />} />
    </Routes>
  </BrowserRouter>
}

// 用来作为 404 页面的组件
const NotFound = () => {
  return <div>你来到了没有知识的荒原</div>
}

默认二级路由

子路由路径不以斜杠开头 :这样可以确保子路由路径是相对于父路由的路径来解析的,避免路由匹配出现问题。
导航路径 :在 Link 组件中,确保 to 属性的路径是正确的绝对路径。

js 复制代码
import { Link, Outlet } from "react-router-dom"
const Layout = ()=>{
    return (
        <div>
            layout
            <Link to='son1'><button>去son1</button></Link>
            <br></br>
            {/* <Link to='/son2'><button>去son2</button></Link> */}
            <Link to='/layout'><button>去son2</button></Link>
            <Outlet></Outlet>
        </div>
    )
}
export default Layout


import { createBrowserRouter } from "react-router-dom";
import Login from "../page/Login";
import Home from "../page/Home";
import Son1 from "../page/son1";
import Son2 from "../page/son2";
import Layout from "../page/Layout";
export const router = createBrowserRouter([
    {path:'/login',element:<Login></Login>},
    {path:'/home/:id/:name',element:<Home></Home>},
    {
        path:'/layout',
        element:<Layout></Layout>,
        children:[
            {path:'son1',element:<Son1></Son1>},
            // {path:'son2',element:<Son2></Son2>}
            {index:true,element:<Son2></Son2>}
        ]   
    },
])

路由懒加载


AuthRoute

AuthRoute 大概率是开发者自定义的一个组件 。通常其作用是进行权限验证相关操作,用于控制哪些用户能够访问特定路由对应的页面。比如:

  • 实现方式:它内部可能会检查用户的登录状态(例如查看是否存在有效的用户令牌 )、用户角色(比如区分普通用户和管理员 )等信息。如果用户满足访问条件(已登录且具有相应权限 ),则允许渲染其包裹的子组件(这里是 Layout 组件 );若不满足条件,可能会将用户重定向到登录页或提示无权限访问。示例代码如下:
js 复制代码
import React from'react';
import { Navigate } from'react-router-dom';

// 假设从 Redux 或其他状态管理获取用户登录状态
import { useSelector } from'react-redux'; 

const AuthRoute = ({ children }) => {
  const isLoggedIn = useSelector(state => state.user.isLoggedIn);
  if (!isLoggedIn) {
    return <Navigate to="/login" />;
  }
  return children;
};

export default AuthRoute;

Suspense

  • Suspense 是 React 内置组件 ,主要用于处理异步加载组件时的过渡状态。在 React Router 场景下,常配合动态导入组件(实现代码分割 )使用,作用和特点如下:

  • 作用:当组件(如 Home、Article、Publish 组件 )是通过动态导入(如 const Home = React.lazy(() => import('./Home')) )这种异步方式获取时,在组件实际加载完成之前,Suspense 可以展示一个 fallback 内容(这里设置为 '加载中' ),向用户提示页面正在加载,提升用户体验。

  • 使用方法:需要包裹异步加载的组件,并通过 fallback 属性指定加载过程中显示的内容。当被包裹组件成功加载后,Suspense 会渲染该组件;若加载失败,可配合错误边界(ErrorBoundary 组件 )进行错误处理。示例:

js 复制代码
import React, { Suspense } from'react';

// 动态导入组件
const Home = React.lazy(() => import('./Home')); 

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Home />
    </Suspense>
  );
}

export default App;

常用的HOOK函数(高阶组件优化)

useMemo


React.memo


memo进行缓存,只有props发生变化的时候才会重新渲染

React.memo-props比较机制

复制代码
数组是对象,属于引用类型(reference type)。
当你使用 useState Hook 来创建一个数组时,这个数组的引用在组件的整个生命周期中是不变的。
这意味着,只要数组的内容没有被显式地修改,数组的内存地址(引用)就不会改变。

机制


当传递的是对象时,比较的是新值和旧值的引用是否相同

因为每当对象发生变化时,父组件会重新执行,父组件重新执行会引发对象重新执行,进而导致对象的引用不相同,导致子组件重新渲染

可通过使用useMemo在组建渲染的过程中缓存一个值

js 复制代码
import {memo, useMemo, useState} from'react'

const MemoSon = memo (function Son ({ list}) {
console.log (' 子组件重新渲染了 ')
return <div>this is Son {list}</div>
})

function App() {
	const [count, setCount] = useState(0)

	// const num = 100

	const list = useMemo(() => {
	return [1, 2, 3]
	}, [])
}

useMemo 接收两个参数,第一个参数是一个回调函数,用于返回要缓存的值;第二个参数是一个依赖数组,这里依赖数组为空数组 [],表示只要组件重新渲染,useMemo 都会计算并返回缓存值(因为没有依赖任何会变化的值 )。如果依赖数组中有其他变量,只有当这些变量发生变化时,useMemo 才会重新计算缓存值。

useCallback

复制代码
useState 更适合管理那些需要频繁更新并且每次更新都需要反映在 UI 上的状态。
useMemo 更适合用于性能优化,避免不必要的计算和渲染。
  • 在组件多次重新渲染的时候缓存函数**

  • 使用useCallback包裹函数之后,函数可以保证APP重新渲染的时候保持引用稳定

  • useMemo 完全可以用来缓存函数 ,但它的主要设计目的是缓存任意类型的值(包括函数)。而 useCallback 实际上是 useMemo 的语法糖,专门为缓存函数设计,让代码更简洁直观。

  • https://yuanbao.tencent.com/bot/app/share/chat/Ddkl3In8Me63

React.forwardRef

useRef

js 复制代码
import { useEffect, useRef} from "react";

function App() {
  const IptRef = useRef(null);
  useEffect(()=>{
    console.log("IptRef", IptRef,IptRef.current);
  })
  console.log("IptRef", IptRef,IptRef.current);
  
  return (
    <div>
      <input type="text" ref={IptRef} />
    </div>
  );
}

export default App;

useInperativeHandle

useImperativeHandle 是一个 React Hook,它用于在子组件中自定义通过 ref 暴露给父组件的实例方法或属性 。通过 useImperativeHandle,可以有选择性地将子组件的某些功能或数据暴露给父组件,而不是直接将整个子组件实例或 DOM 元素暴露出去,从而更好地控制子组件对外的接口。

通过ref暴露子组件中的方法

js 复制代码
const Input = forwardRef((props, ref) => {
  const inputRef = useRef(null)
  // 实现聚焦逻辑函数
  const focusHandler = () => {
    inputRef.current.focus()
  }
  // 暴露函数给父组件调用
  useImperativeHandle(ref, () => {
    return {
      focusHandler
    }
  })
  return <input type="text" ref={inputRef} />
})

// 父组件
function App() {
  const sonRef = useRef(null)
  const focusHandler = () => {
    console.log(sonRef.current)
    sonRef.current.focusHandler()
  }
  return (
    <>
      <Son ref={sonRef} />
      <button onClick={focusHandler}>focus</button>
    </>
  )
}

forwardRef 和 useImperativeHandle的区别

  • 目的不同:forwardRef 主要是为了实现 ref 的传递,让父组件能够直接引用子组件内部的元素或实例;useImperativeHandle 则是用于在子组件中定制通过 ref 暴露给父组件的内容。
  • 使用位置不同:forwardRef 是一个高阶函数,用于包裹组件定义;useImperativeHandle 是一个 Hook,只能在函数组件内部使用。
  • 对组件封装性的影响不同:forwardRef 一定程度上打破了组件的封装,让父组件能直接访问子组件内部;useImperativeHandle 则在保证封装性的前提下,有选择地暴露功能给父组件 。
相关推荐
Komorebi゛几秒前
【CSS】圆锥渐变流光效果边框样式实现
前端·css
工藤学编程13 分钟前
零基础学AI大模型之CoT思维链和ReAct推理行动
前端·人工智能·react.js
徐同保13 分钟前
上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片
前端·javascript·pdf
怕浪猫14 分钟前
React从入门到出门第四章 组件通讯与全局状态管理
前端·javascript·react.js
博主花神15 分钟前
【React】扩展知识点
javascript·react.js·ecmascript
欧阳天风22 分钟前
用setTimeout代替setInterval
开发语言·前端·javascript
EndingCoder25 分钟前
箭头函数和 this 绑定
linux·前端·javascript·typescript
郑州光合科技余经理26 分钟前
架构解析:同城本地生活服务o2o平台海外版
大数据·开发语言·前端·人工智能·架构·php·生活
沐墨染28 分钟前
大型数据分析组件前端实践:多维度检索与实时交互设计
前端·elementui·数据挖掘·数据分析·vue·交互
xkxnq31 分钟前
第一阶段:Vue 基础入门(第 11 天)
前端·javascript·vue.js