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 则在保证封装性的前提下,有选择地暴露功能给父组件 。
相关推荐
kooboo china.1 小时前
Tailwind css实战,基于Kooboo构建AI对话框页面(二)
前端·css
啃火龙果的兔子2 小时前
判断手机屏幕上的横向滑动(左滑和右滑)
javascript·react.js·智能手机
yuanmenglxb20044 小时前
react基础技术栈
前端·javascript·react.js
coding随想4 小时前
从SPDY到HTTP/2:网络协议的革新与未来
javascript
Magnum Lehar5 小时前
vulkan游戏引擎vulkan部分的fence实现
java·前端·游戏引擎
一枚码农4045 小时前
使用pnpm、vite搭建Phaserjs的开发环境
javascript·游戏·vite·phaserjs
FreeBuf_5 小时前
恶意npm与VS Code包窃取数据及加密货币资产
前端·npm·node.js
agenIT6 小时前
vue3 getcurrentinstance 用法
javascript·vue.js·ecmascript
天天打码6 小时前
npm/yarn/pnpm安装时Sharp模块报错解决方法
前端·npm·node.js
码农捻旧6 小时前
JavaScript 性能优化按层次逐步分析
开发语言·前端·javascript·性能优化