107

Code

const addPerson = (event) => {
    // event.preventDefault();
    const personObject = {
      name: newName,
      id: persons.length + 1,
      number: newNumber,
    };
    if (persons.some((person) => person.name === newName)) {
      if (
        window.confirm(
          `${newName} is already in the phonebook, replace the old number with new one? `
        )
      ) {
        const person = persons.find((person) => person.name === newName);
        const updatedPerson = { ...person, number: newNumber };
        axios
          .put(`http://localhost:3002/persons/${person.id}`, updatedPerson)
          .then((response) => {
            const responseData = response.data;
            setPersons((prev) =>
              prev.map((p) => (p.id === person.id ? responseData : p))
            );
          });
      }
    } else {
      axios
        .post("http://localhost:3002/persons", personObject)
        .then((response) => {
          const responseData = response.data;
          setPersons(responseData.concat(personObject));
          setNewName("");
          setNewNumber("");
        });
    }

功能描述

这是一个 React 电话簿应用中的添加/更新联系人功能,支持新增联系人或更新已存在联系人的电话号码。

核心逻辑流程

  1. 创建联系人对象

    • 使用表单输入的 newNamenewNumber 创建 personObject
    • ID 自动设置为 persons.length + 1
  2. 检查重名逻辑

    persons.some((person) => person.name === newName);
    
    • 如果名字已存在 → 进入更新流程
    • 如果名字不存在 → 进入新增流程
  3. 更新流程(重名时)

    • 弹出确认对话框 window.confirm()
    • 用户确认后:
      • find() 找到原有联系人
      • 创建 updatedPerson 对象(保留 id,更新 number)
      • 发送 PUT 请求http://localhost:3002/persons/{id}
      • 更新成功后,用 map() 更新本地 state
  4. 新增流程(无重名)

    • 发送 POST 请求http://localhost:3002/persons
    • 成功后:
      • 将新数据合并到 persons 数组
      • 清空表单输入 setNewName("")setNewNumber("")

涉及知识点

1. JavaScript 数组方法

  • Array.some(): 检查数组中是否有元素满足条件(返回 boolean)
  • Array.find(): 查找第一个满足条件的元素(返回元素本身)
  • Array.map(): 遍历数组并返回新数组,用于不可变更新 state
  • Array.concat(): 合并数组,返回新数组

2. React 核心概念

  • State 管理: setPersons 更新联系人列表
  • 不可变性原则:
    • 使用展开运算符 { ...person, number: newNumber }
    • 使用 map() 而不是直接修改数组
  • 函数式 setState: setPersons((prev) => ...),基于前一个状态更新

3. HTTP 请求(axios)

  • POST 请求: 创建新资源
    axios.post(url, data).then(response => {...})
    
  • PUT 请求: 更新已有资源
    axios.put(url, updatedData).then(response => {...})
    
  • Promise 处理: 使用 .then() 处理异步响应

4. 用户交互

  • window.confirm(): 浏览器原生确认对话框
  • 条件渲染/执行: 根据用户选择执行不同逻辑

5. ES6+ 语法

  • 箭头函数: (event) => {...}
  • 模板字符串: `${newName} is already...`
  • 对象展开运算符: { ...person, number: newNumber }
  • 三元运算符(隐含): map() 中的条件判断 p.id === person.id ? responseData : p

潜在问题/改进点

  1. ⚠️ ID 生成方式不安全

    • id: persons.length + 1 在删除联系人后可能导致 ID 冲突
    • 应该由后端生成或使用 UUID
  2. ⚠️ 新增流程的数据冗余

    • 第 42 行:responseData.concat(personObject)
    • 应该只使用服务器返回的数据:setPersons(persons.concat(responseData))
  3. ⚠️ 缺少错误处理

    • 没有 .catch() 处理请求失败的情况
  4. ⚠️ 注释掉的代码

    • // event.preventDefault(); 被注释,可能导致表单提交时页面刷新

关键代码片段解析

对象更新(不可变方式)

const updatedPerson = { ...person, number: newNumber };

创建新对象,保留原有属性,只更新 number 字段

State 的条件更新

setPersons((prev) => prev.map((p) => (p.id === person.id ? responseData : p)));

只更新匹配 ID 的元素,其他元素保持不变