国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

React 18 遷移狀態(tài)邏輯至 Reducer 中

這篇具有很好參考價值的文章主要介紹了React 18 遷移狀態(tài)邏輯至 Reducer 中。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

參考文章

遷移狀態(tài)邏輯至 Reducer 中

對于擁有許多狀態(tài)更新邏輯的組件來說,過于分散的事件處理程序可能會令人不知所措。對于這種情況,可以將組件的所有狀態(tài)更新邏輯整合到一個外部函數(shù)中,這個函數(shù)叫作 reducer。

使用 reducer 整合狀態(tài)邏輯

隨著組件復(fù)雜度的增加,將很難一眼看清所有的組件狀態(tài)更新邏輯。例如,下面的 TaskApp 組件有一個數(shù)組類型的狀態(tài) tasks,并通過三個不同的事件處理程序來實(shí)現(xiàn)任務(wù)的添加、刪除和修改:

import { useState } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, setTasks] = useState(initialTasks);

  function handleAddTask(text) {
    setTasks([
      ...tasks,
      {
        id: nextId++,
        text: text,
        done: false,
      },
    ]);
  }

  function handleChangeTask(task) {
    setTasks(
      tasks.map((t) => {
        if (t.id === task.id) {
          return task;
        } else {
          return t;
        }
      })
    );
  }

  function handleDeleteTask(taskId) {
    setTasks(tasks.filter((t) => t.id !== taskId));
  }

  return (
    <>
      <h1>布拉格的行程安排</h1>
      <AddTask onAddTask={handleAddTask} />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

let nextId = 3;
const initialTasks = [
  {id: 0, text: '參觀卡夫卡博物館', done: true},
  {id: 1, text: '看木偶戲', done: false},
  {id: 2, text: '打卡列儂墻', done: false},
];

這個組件的每個事件處理程序都通過 setTasks 來更新狀態(tài)。隨著這個組件的不斷迭代,其狀態(tài)邏輯也會越來越多。為了降低這種復(fù)雜度,并讓所有邏輯都可以存放在一個易于理解的地方,可以將這些狀態(tài)邏輯移到組件之外的一個稱為 reducer 的函數(shù)中。

Reducer 是處理狀態(tài)的另一種方式??梢酝ㄟ^三個步驟將 useState 遷移到 useReducer

  1. 將設(shè)置狀態(tài)的邏輯 修改 成 dispatch 的一個 action;
  2. 編寫 一個 reducer 函數(shù);
  3. 在組件中 使用 reducer。

第 1 步: 將設(shè)置狀態(tài)的邏輯修改成 dispatch 的一個 action

事件處理程序目前是通過設(shè)置狀態(tài)來 實(shí)現(xiàn)邏輯的

function handleAddTask(text) {
  setTasks([
    ...tasks,
    {
      id: nextId++,
      text: text,
      done: false,
    },
  ]);
}

function handleChangeTask(task) {
  setTasks(
    tasks.map((t) => {
      if (t.id === task.id) {
        return task;
      } else {
        return t;
      }
    })
  );
}

function handleDeleteTask(taskId) {
  setTasks(tasks.filter((t) => t.id !== taskId));
}

移除所有的狀態(tài)設(shè)置邏輯。只留下三個事件處理函數(shù):

  • handleAddTask(text) 在用戶點(diǎn)擊 “添加” 時被調(diào)用。
  • handleChangeTask(task) 在用戶切換任務(wù)或點(diǎn)擊 “保存” 時被調(diào)用。
  • handleDeleteTask(taskId) 在用戶點(diǎn)擊 “刪除” 時被調(diào)用。

使用 reducers 管理狀態(tài)與直接設(shè)置狀態(tài)略有不同。它不是通過設(shè)置狀態(tài)來告訴 React “要做什么”,而是通過事件處理程序 dispatch 一個 “action” 來指明 “用戶剛剛做了什么”。(而狀態(tài)更新邏輯則保存在其他地方?。┮虼耍辉偻ㄟ^事件處理器直接 “設(shè)置 task”,而是 dispatch 一個 “添加/修改/刪除任務(wù)” 的 action。這更加符合用戶的思維。

function handleAddTask(text) {
  dispatch({
    type: 'added',
    id: nextId++,
    text: text,
  });
}

function handleChangeTask(task) {
  dispatch({
    type: 'changed',
    task: task,
  });
}

傳遞給 dispatch 的對象叫做 “action”:

function handleDeleteTask(taskId) {
  dispatch(
    // "action" 對象:
    {
      type: 'deleted',
      id: taskId,
    }
  );
}

它是一個普通的 JavaScript 對象。它的結(jié)構(gòu)是由你決定的,但通常來說,它應(yīng)該至少包含可以表明 發(fā)生了什么事情 的信息。

注意:action 對象可以有多種結(jié)構(gòu)。

按照慣例,通常會添加一個字符串類型的 type 字段來描述發(fā)生了什么,并通過其它字段傳遞額外的信息。type 是特定于組件的,在這個例子中 addedaddded_task 都可以。選一個能描述清楚發(fā)生的事件的名字!

dispatch({
  // 針對特定的組件
  type: 'what_happened',
  // 其它字段放這里
});

第 2 步: 編寫一個 reducer 函數(shù)

reducer 函數(shù)就是放置狀態(tài)邏輯的地方。它接受兩個參數(shù),分別為當(dāng)前 state 和 action 對象,并且返回的是更新后的 state:

function yourReducer(state, action) {
  // 給 React 返回更新后的狀態(tài)
}

React 會將狀態(tài)設(shè)置為從 reducer 返回的狀態(tài)。

在這個例子中,要將狀態(tài)設(shè)置邏輯從事件處理程序移到 reducer 函數(shù)中,需要:

  1. 聲明當(dāng)前狀態(tài)(tasks)作為第一個參數(shù);
  2. 聲明 action 對象作為第二個參數(shù);
  3. reducer 返回 下一個 狀態(tài)(React 會將舊的狀態(tài)設(shè)置為這個最新的狀態(tài))。

下面是所有遷移到 reducer 函數(shù)的狀態(tài)設(shè)置邏輯:

function tasksReducer(tasks, action) {
  if (action.type === 'added') {
    return [
      ...tasks,
      {
        id: action.id,
        text: action.text,
        done: false,
      },
    ];
  } else if (action.type === 'changed') {
    return tasks.map((t) => {
      if (t.id === action.task.id) {
        return action.task;
      } else {
        return t;
      }
    });
  } else if (action.type === 'deleted') {
    return tasks.filter((t) => t.id !== action.id);
  } else {
    throw Error('未知 action: ' + action.type);
  }
}

由于 reducer 函數(shù)接受 state(tasks)作為參數(shù),因此可以 在組件之外聲明它。這減少了代碼的縮進(jìn)級別,提升了代碼的可讀性。

注意:上面的代碼使用了 if/else 語句,但是在 reducers 中使用 switch 語句 是一種慣例。兩種方式結(jié)果是相同的,但 switch 語句讀起來一目了然。

以后會像這樣使用:

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [
        ...tasks,
        {
          id: action.id,
          text: action.text,
          done: false,
        },
      ];
    }
    case 'changed': {
      return tasks.map((t) => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error('未知 action: ' + action.type);
    }
  }
}

建議將每個 case 塊包裝到 {} 花括號中,這樣在不同 case 中聲明的變量就不會互相沖突。此外,case 通常應(yīng)該以 return 結(jié)尾。如果忘了 return,代碼就會 進(jìn)入 到下一個 case,這就會導(dǎo)致錯誤!

如果還不熟悉 switch 語句,使用 if/else 也是可以的。

第 3 步: 在組件中使用 reducer

最后,需要將 tasksReducer 導(dǎo)入到組件中。記得先從 React 中導(dǎo)入 useReducer Hook:

import { useReducer } from 'react';

接下來,就可以替換掉之前的 useState:

const [tasks, setTasks] = useState(initialTasks);

只需要像下面這樣使用 useReducer:

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

useReduceruseState 很相似——必須給它傳遞一個初始狀態(tài),它會返回一個有狀態(tài)的值和一個設(shè)置該狀態(tài)的函數(shù)(在這個例子中就是 dispatch 函數(shù))。但是,它們兩個之間還是有點(diǎn)差異的。

useReducer 鉤子接受 2 個參數(shù):

  1. 一個 reducer 函數(shù)
  2. 一個初始的 state

它返回如下內(nèi)容:

  1. 一個有狀態(tài)的值
  2. 一個 dispatch 函數(shù)(用來 “派發(fā)” 用戶操作給 reducer)

現(xiàn)在一切都準(zhǔn)備就緒了!在這里把 reducer 定義在了組件的末尾:

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task,
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId,
    });
  }

  return (
    <>
      <h1>布拉格的行程安排</h1>
      <AddTask onAddTask={handleAddTask} />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [
        ...tasks,
        {
          id: action.id,
          text: action.text,
          done: false,
        },
      ];
    }
    case 'changed': {
      return tasks.map((t) => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error('未知 action: ' + action.type);
    }
  }
}

...

如果有需要,甚至可以把 reducer 移到一個單獨(dú)的文件中:

// tasksReducer.js
export default function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [
        ...tasks,
        {
          id: action.id,
          text: action.text,
          done: false,
        },
      ];
    }
    case 'changed': {
      return tasks.map((t) => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error('未知 action:' + action.type);
    }
  }
}

當(dāng)像這樣分離關(guān)注點(diǎn)時,我們可以更容易地理解組件邏輯?,F(xiàn)在,事件處理程序只通過派發(fā) action 來指定 發(fā)生了什么,而 reducer 函數(shù)通過響應(yīng) actions 來決定 狀態(tài)如何更新。

對比 useState 和 useReducer

Reducers 并非沒有缺點(diǎn)!以下是比較它們的幾種方法:

  • 代碼體積: 通常,在使用 useState 時,一開始只需要編寫少量代碼。而 useReducer 必須提前編寫 reducer 函數(shù)和需要調(diào)度的 actions。但是,當(dāng)多個事件處理程序以相似的方式修改 state 時,useReducer 可以減少代碼量。
  • 可讀性: 當(dāng)狀態(tài)更新邏輯足夠簡單時,useState 的可讀性還行。但是,一旦邏輯變得復(fù)雜起來,它們會使組件變得臃腫且難以閱讀。在這種情況下,useReducer 允許將狀態(tài)更新邏輯與事件處理程序分離開來。
  • 可調(diào)試性: 當(dāng)使用 useState 出現(xiàn)問題時, 很難發(fā)現(xiàn)具體原因以及為什么。 而使用 useReducer 時, 可以在 reducer 函數(shù)中通過打印日志的方式來觀察每個狀態(tài)的更新,以及為什么要更新(來自哪個 action)。 如果所有 action 都沒問題,就知道問題出在了 reducer 本身的邏輯中。 然而,與使用 useState 相比,必須單步執(zhí)行更多的代碼。
  • 可測試性: reducer 是一個不依賴于組件的純函數(shù)。這就意味著可以單獨(dú)對它進(jìn)行測試。一般來說,我們最好是在真實(shí)環(huán)境中測試組件,但對于復(fù)雜的狀態(tài)更新邏輯,針對特定的初始狀態(tài)和 action,斷言 reducer 返回的特定狀態(tài)會很有幫助。
  • 個人偏好: 并不是所有人都喜歡用 reducer,沒關(guān)系,這是個人偏好問題??梢噪S時在 useStateuseReducer 之間切換,它們能做的事情是一樣的!

如果在修改某些組件狀態(tài)時經(jīng)常出現(xiàn)問題或者想給組件添加更多邏輯時,建議還是使用 reducer。當(dāng)然,也不必整個項(xiàng)目都用 reducer,這是可以自由搭配的。甚至可以在一個組件中同時使用 useStateuseReducer。

編寫一個好的 reducers

編寫 reducers 時最好牢記以下兩點(diǎn):

  • reducers 必須是純粹的。 這一點(diǎn)和 狀態(tài)更新函數(shù) 是相似的,reducers 在是在渲染時運(yùn)行的?。╝ctions 會排隊(duì)直到下一次渲染)。 這就意味著 reducers 必須純凈,即當(dāng)輸入相同時,輸出也是相同的。它們不應(yīng)該包含異步請求、定時器或者任何副作用(對組件外部有影響的操作)。它們應(yīng)該以不可變值的方式去更新 對象 和 數(shù)組。
  • 每個 action 都描述了一個單一的用戶交互,即使它會引發(fā)數(shù)據(jù)的多個變化。 舉個例子,如果用戶在一個由 reducer 管理的表單(包含五個表單項(xiàng))中點(diǎn)擊了 重置按鈕,那么 dispatch 一個 reset_form 的 action 比 dispatch 五個單獨(dú)的 set_field 的 action 更加合理。如果在一個 reducer 中打印了所有的 action 日志,那么這個日志應(yīng)該是很清晰的,它能讓你以某種步驟復(fù)現(xiàn)已發(fā)生的交互或響應(yīng)。這對代碼調(diào)試很有幫助!

使用 Immer 簡化 reducers

與在平常的 state 中 修改對象 和 數(shù)組 一樣,可以使用 Immer 這個庫來簡化 reducer。在這里,useImmerReducer 讓你可以通過 pusharr[i] = 來修改 state :

import { useImmerReducer } from 'use-immer';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

function tasksReducer(draft, action) {
  switch (action.type) {
    case 'added': {
      draft.push({
        id: action.id,
        text: action.text,
        done: false,
      });
      break;
    }
    case 'changed': {
      const index = draft.findIndex((t) => t.id === action.task.id);
      draft[index] = action.task;
      break;
    }
    case 'deleted': {
      return draft.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error('未知 action:' + action.type);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useImmerReducer(tasksReducer, initialTasks);

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task,
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId,
    });
  }

  return (
    <>
      <h1>布拉格的行程安排</h1>
      <AddTask onAddTask={handleAddTask} />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

...

Reducers 應(yīng)該是純凈的,所以它們不應(yīng)該去修改 state。而 Immer 提供了一種特殊的 draft 對象,可以通過它安全的修改 state。在底層,Immer 會基于當(dāng)前 state 創(chuàng)建一個副本。這就是為什么通過 useImmerReducer 來管理 reducers 時,可以修改第一個參數(shù),且不需要返回一個新的 state 的原因。文章來源地址http://www.zghlxwxcb.cn/news/detail-690151.html

摘要

  • useState 轉(zhuǎn)化為 useReducer
    1. 通過事件處理函數(shù) dispatch actions;
    2. 編寫一個 reducer 函數(shù),它接受傳入的 state 和一個 action,并返回一個新的 state;
    3. 使用 useReducer 替換 useState;
  • Reducers 可能需要寫更多的代碼,但是這有利于代碼的調(diào)試和測試。
  • Reducers 必須是純凈的。
  • 每個 action 都描述了一個單一的用戶交互。
  • 使用 Immer 來幫助在 reducer 里直接修改狀態(tài)。

到了這里,關(guān)于React 18 遷移狀態(tài)邏輯至 Reducer 中的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • React 18 state 狀態(tài)更新函數(shù)

    參考文章 設(shè)置組件 state 會把一次重新渲染加入隊(duì)列。但有時可能會希望在下次渲染加入隊(duì)列之前對 state 的值執(zhí)行多次操作。為此,了解 React 如何批量更新 state 會很有幫助。 在下面的示例中,可能會認(rèn)為點(diǎn)擊 “+3” 按鈕會使計(jì)數(shù)器遞增三次,因?yàn)樗{(diào)用了 setNumber(number +

    2024年02月13日
    瀏覽(25)
  • React的hooks---useReducer

    useReducer 作為 useState 的代替方案,在某些場景下使用更加適合,例如 state 邏輯較復(fù)雜且包含多個子值,或者下一個 state 依賴于之前的 state 等。 使用 useReducer 還能給那些會觸發(fā)深更新的組件做性能優(yōu)化,因?yàn)楦附M件可以向自組件傳遞 dispatch 而不是回調(diào)函數(shù) useReducer 初始化

    2024年02月15日
    瀏覽(23)
  • React 新版官方文檔 (一) useReducer 用法詳解

    React 新版官方文檔 (一) useReducer 用法詳解

    useReducer 是一個可以讓你向組件中添加 reducer 的 Hook 基本用法 比 useState 多了一個處理函數(shù),該函數(shù)可以根據(jù)不同的分發(fā)狀態(tài)來對應(yīng)的改變狀態(tài) 注意:state 不可修改 不能這樣寫, reducer 函數(shù)應(yīng)當(dāng)返回一個新對象 不要重新執(zhí)行初始函數(shù) 第一種寫法會導(dǎo)致每次渲染時候都調(diào)用

    2024年02月13日
    瀏覽(23)
  • 【前端】React快速入門+Redux狀態(tài)管理

    【前端】React快速入門+Redux狀態(tài)管理

    本文旨在記錄react的基礎(chǔ)內(nèi)容,幫助有需要的同學(xué)快速上手,需要進(jìn)一步了解描述更加穩(wěn)妥和全面的信息,請查閱官方文檔 官方文檔點(diǎn)擊這里進(jìn)行跳轉(zhuǎn) react框架 vue,react,angular這幾種主流前端框架使用頻率較高…本質(zhì)還是js庫。 React.js是一個用于構(gòu)建用戶界面的JavaScript庫。它由

    2024年02月12日
    瀏覽(60)
  • qiankun:react18主應(yīng)用 + 微應(yīng)用 react18 + vue3

    qiankun:react18主應(yīng)用 + 微應(yīng)用 react18 + vue3

    一:主應(yīng)用 搭建react項(xiàng)目 安裝Antd 在 index.js中引入 安裝react-router : 在 index.js中引入 安裝 qiankun : 在主應(yīng)用中注冊微應(yīng)用,在 index.js中引入 注:子應(yīng)用嵌入到主應(yīng)用的地方,id要跟index.js下registerMicroApps里面的container設(shè)置一致 修改App.js文件,將如下代碼放入App.js 修改App.css樣

    2024年02月16日
    瀏覽(43)
  • React18入門(第二篇)——React18+Ts項(xiàng)目配置husky、eslint、pretttier、commitLint

    React18入門(第二篇)——React18+Ts項(xiàng)目配置husky、eslint、pretttier、commitLint

    我的項(xiàng)目版本如下: React: V18.2.0 Node.js: V16.14.0 TypeScript:最新版 工具: VsCode 本文將采用圖文詳解的方式,手把手帶你快速完成在React項(xiàng)目中配置husky、prettier、commitLint,實(shí)現(xiàn)編碼規(guī)范的統(tǒng)一,git提交規(guī)范的統(tǒng)一。 1.1 裝包 1.2 ESLint 插件安裝 1.3 創(chuàng)建命令并使用 新增命令 執(zhí)行

    2024年02月08日
    瀏覽(28)
  • React源碼解析18(1)------ React.createElement 和 jsx

    React源碼解析18(1)------ React.createElement 和 jsx

    我們知道在React17版本之前,我們在項(xiàng)目中是一定需要引入react的。 import React from “react” 即便我們有時候沒有使用到React,也需要引入。原因是什么呢? 在React項(xiàng)目中,如果我們使用了模板語法JSX,我們知道它要先經(jīng)過babel的轉(zhuǎn)譯。那babel會將JSX轉(zhuǎn)換成什么樣子的格式呢? 可

    2024年02月13日
    瀏覽(21)
  • 創(chuàng)建react腳手架項(xiàng)目——demo(react18 + react-router 6)+ react項(xiàng)目打包部署

    創(chuàng)建react腳手架項(xiàng)目——demo(react18 + react-router 6)+ react項(xiàng)目打包部署

    全局安裝 create-react-app 說明:不建議安裝全局,建議使用 npx 命令安裝,具體可參考官網(wǎng),如下: 官網(wǎng): https://zh-hans.legacy.reactjs.org/docs/create-a-new-react-app.html. 1.2.1 問題1——npm ERR! code ENOTFOUND(網(wǎng)絡(luò)問題clashx) 問題描述,如下: 解決問題——方式1 如果使用了clashx,可能是它

    2024年02月07日
    瀏覽(30)
  • react 實(shí)現(xiàn)監(jiān)聽邏輯

    需求: 在一個頁面下有多個子tab 在某些tab 下,或者父節(jié)點(diǎn)的數(shù)據(jù)更新的時候,其他子tab 或者父節(jié)點(diǎn)也要同步更新 進(jìn)程: 正常情況下會把所有用到的數(shù)據(jù)都移動到父節(jié)點(diǎn),修改行為也都放在父節(jié)點(diǎn) 但如果這樣的話父節(jié)點(diǎn)的數(shù)據(jù)會非常的多,而且有可能只是某兩個子節(jié)點(diǎn)的數(shù)

    2024年02月09日
    瀏覽(31)
  • 【react】react18的學(xué)習(xí)(十一)– 底層原理(一)之 diff 算法

    diff算法、fiber鏈表 步驟:(追求多復(fù)用,快渲染) 首次渲染,緩存虛擬dom或fiber鏈表(17及以后); 組件更新,將新生成的虛擬dom與已有的真實(shí)dom的fiber鏈表對比; 遵循同級對比、深度對比原則,先依次找節(jié)點(diǎn)對比; 對比過程中,第一輪對比:按鏈表順序?qū)Ρ?,?jié)點(diǎn)key值相

    2024年02月17日
    瀏覽(18)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包