【React】React学习:从初级到高级(三)

3 状态管理

随着应用不断变大,应该更有意识的去关注应用状态如何组织,以及数据如何在组件之间流动。冗余或重复的状态往往是缺陷的根源。

3.1 用State响应输入

3.1.1 声明式地考虑UI

总体步骤如下:

  1. 定位组件中不同的视图状态

  2. 确定是什么触发了这些 state 的改变

    • 人为输入,人为输入一般需要事件处理函数
    • 计算机输入
  3. 表示内存中的 state(需要使用 useState

    • 需要让“变化的部分”尽可能的少,可以先从必须的状态开始
    • 刚开始若不确定可以设置多一些视图状态,之后不断尝试重构state
  4. 删除任何不必要的 state 变量

    • 这个 state 是否会导致矛盾

    • 相同的信息是否已经在另一个 state 变量中存在

    • 你是否可以通过另一个 state 变量的相反值得到相同的信息

      Reducer 可以合并多个状态变量到一个对象中并巩固所有相关的逻辑!

  5. 连接事件处理函数去设置 state

    • 创建事件处理函数去设置 state 变量。

同时展示大量的视图状态,这样的页面通常被称作living styleguide或者storybook


挑战:这个表单在两种模式间切换:编辑模式,你可以看到输入框;查看模式,你只能看到结果。按钮的标签会根据你所处的模式在“编辑”和“保存”两者中切换。当你改变输入框的内容时,欢迎信息会最下面实时更新。

import {useState} from 'react';export default function EditProfile() {const [display, setDisplay] = useState(false);const [person, setPerson] = useState({firstName: 'Joe',lastName: 'Stan'});function handleFirstNameChange(e) {setPerson({...person,[e.target.name]: e.target.value})}function handleLastNameChange(e) {setPerson({...person,[e.target.name]: e.target.value})}return (<form onSubmit={e => {e.preventDefault(),setDisplay(!display)}}><label>First name:{' '}{display ? <input name="firstName"value={person.firstName}onChange={handleFirstNameChange} /> :<b>{person.firstName}</b>}</label><label>Last name:{' '}{display ? <input name="lastName" value={person.lastName}onChange={handleLastNameChange} /> :<b>{person.lastName}</b>}</label><button type="submit">{display ? 'Save' : 'Edit'} Profile</button><p><i>Hello, {person.firstName} {person.lastName}!</i></p></form>);
}

updateDOM函数展示了当你设置 state 时,React 在幕后都做了什么.

// index.js
let firstName = 'Jane';
let lastName = 'Jacobs';
let isEditing = false;function handleFormSubmit(e) {e.preventDefault();setIsEditing(!isEditing);
}function handleFirstNameChange(e) {setFirstName(e.target.value);
}function handleLastNameChange(e) {setLastName(e.target.value);
}function setFirstName(value) {firstName = value;updateDOM();
}function setLastName(value) {lastName = value;updateDOM();
}function setIsEditing(value) {isEditing = value;updateDOM();
}function updateDOM() {if (isEditing) {editButton.textContent = 'Save Profile';hide(firstNameText);hide(lastNameText);show(firstNameInput);show(lastNameInput);} else {editButton.textContent = 'Edit Profile';hide(firstNameInput);hide(lastNameInput);show(firstNameText);show(lastNameText);}firstNameText.textContent = firstName;lastNameText.textContent = lastName;helloText.textContent = ('Hello ' +firstName + ' ' +lastName + '!');
}function hide(el) {el.style.display = 'none';
}function show(el) {el.style.display = '';
}let form = document.getElementById('form');
let editButton = document.getElementById('editButton');
let firstNameInput = document.getElementById('firstNameInput');
let firstNameText = document.getElementById('firstNameText');
let lastNameInput = document.getElementById('lastNameInput');
let lastNameText = document.getElementById('lastNameText');
let helloText = document.getElementById('helloText');
form.onsubmit = handleFormSubmit;
firstNameInput.oninput = handleFirstNameChange;
lastNameInput.oninput = handleLastNameChange;
/* index.html */
<form id="form"><label>First name:<b id="firstNameText">Jane</b><inputid="firstNameInput"value="Jane"style="display: none"></label><label>Last name:<b id="lastNameText">Jacobs</b><inputid="lastNameInput"value="Jacobs"style="display: none"></label><button type="submit" id="editButton">Edit Profile</button><p><i id="helloText">Hello, Jane Jacobs!</i></p>
</form><style>
* { box-sizing: border-box; }
body { font-family: sans-serif; margin: 20px; padding: 0; }
label { display: block; margin-bottom: 20px; }
</style>

3.2 选择state结构

3.1.1 构建state的原则

  • 合并关联的 state

    • 如果某两个 state 变量总是一起变化,则将它们统一成一个 state 变量可能更好
    • 另一种情况是,将数据整合到一个对象或一个数组中时,不知道需要多少个 state 片段
  • 避免互相矛盾的 state

  • 避免冗余的 state

    • “镜像”一些 prop 属性会导致混淆,建议使用常量

      function Message({ messageColor }) {const color = messageColor;
      

      只有当想要 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 initialdefault 开头,以阐明该 prop 的新值将被忽略:

      function Message({ initialColor }) {// 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。// 对于 `initialColor` 属性的进一步更改将被忽略。const [color, setColor] = useState(initialColor);
      
  • 避免重复的 state

  • 避免深度嵌套的 state


3.3 在组件间共享状态(状态提升)

当编写一个组件时,你应该考虑哪些信息应该由父组件控制(通过传递 props),哪些信息应该由内部state控制(通过 state)。

进行状态提升的步骤:

  1. 从子组件中 移除 state 。
  2. 从父组件 传递 props。
  3. 为子组件的共同父组件添加 state ,并将其与事件处理函数一起向下传递

3.4 对state进行保留和重置

React 使用树形结构来对开发者创造的 UI 进行管理和建模。

在这里插入图片描述

3.4.1 state与树中的某个位置相关联

根据组件在 UI 树中的位置,React 将它所持有的每个 state 与正确的组件关联起来。 React 在移除一个组件时,也会销毁它的 state。下面是一个组件的添加与删除。

在这里插入图片描述
在这里插入图片描述

只要一个组件还被渲染在 UI 树的相同位置,React 就会保留它的 state。 如果它被移除,或者一个不同的组件被渲染在相同的位置,那么 React 就会丢掉它的 state。

3.4.2 相同位置的相同组件会保留state

更新 App 的状态不会重置 Counter,因为 Counter 始终保持在同一位置。

import { useState } from 'react';export default function App() {const [isFancy, setIsFancy] = useState(false);return (<div>{isFancy ? (<Counter isFancy={true} /> ) : (<Counter isFancy={false} /> )}<label><inputtype="checkbox"checked={isFancy}onChange={e => {setIsFancy(e.target.checked)}}/>使用好看的样式</label></div>);
}function Counter({ isFancy }) {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}if (isFancy) {className += ' fancy';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

在这里插入图片描述

对 React 来说重要的是组件在 UI 树中的位置,而不是在 JSX 中的位置!

3.4.3 相同位置的不同组件会重置state

import { useState } from 'react';export default function App() {const [isPaused, setIsPaused] = useState(false);return (<div>{isPaused ? (<p>待会见!</p> ) : (<Counter /> )}<label><inputtype="checkbox"checked={isPaused}onChange={e => {setIsPaused(e.target.checked)}}/>休息一下</label></div>);
}function Counter() {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

在这里插入图片描述

Counter 变为 p 时,Counter 会被移除,同时 p 被添加。

在这里插入图片描述

当切换回来时,p 会被删除,而 Counter 会被添加。

刚开始 <div> 的第一个子组件是一个 Counter。但是当切换成 p 时,React 将 Counter 从 UI 树中移除了并销毁了它的状态。


当在相同位置渲染不同的组件时,组件的整个子树都会被重置

在这里插入图片描述

section 变为 div 时,section 会被删除,新的 div 被添加

在这里插入图片描述

当切换回来时,div 会被删除,新的 section 被添加。

如果想在重新渲染时保留 state,几次渲染中的树形结构就应该相互“匹配”


3.4.4 在相同位置重置相同组件的state

  • 方法1:将组件渲染在不同位置(适用于只有少数几个组件)

在这里插入图片描述

  • 方法2:使用key赋予每个组件一个明确的身份

    {isPlayerA ? (<Counter key="Taylor" person="Taylor" />
    ) : (<Counter key="Sarah" person="Sarah" />
    )}
    

    请记住 key 不是全局唯一的。它们只能指定 父组件内部 的顺序。

3.4.5 使用key重置表单

Chat组件添加一个 key,就可以保证每次选择一个不同的收件人时,Chat组件包括其下方树中的state就会被重新创建。

<Chat key={to.id} contact={to} />

3.4.6 为被移除的组件保留state

比如聊天应用

  • 如果UI比较简单,就可以使用CSS把其他聊天隐藏起来
  • 进行状态提升,让父组件保存信息
  • 使用其他数据源

3.5 迁移状态逻辑至Reducer中

3.5.1 使用reducer整合状态逻辑

Reducer 是处理状态的另一种方式。你可以通过三个步骤将 useState 迁移到 useReducer

  1. 将设置状态的逻辑 修改dispatch 的一个 action

    比如下面这段代码:

    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));
    }
    

    移除所有状态设置逻辑,只留下三个事件处理函数。通过事件处理函数 dispatch 一个 action来指明 “用户刚刚做了什么”。(状态更新逻辑则保存在其他地方!),修改后的代码如下:

    function handleAddTask(text) {dispatch(// action 对象{type: 'added',id: nextId++,text: text,});
    }function handleChangeTask(task) {dispatch({type: 'changed',task: task,});
    }function handleDeleteTask(taskId) {dispatch({type: 'deleted',id: taskId,});
    }
    

    action 对象可以有多种结构。

    按照惯例,通常会添加一个字符串类型的 type 字段来描述发生了什么,并通过其它字段传递额外的信息。type 是特定于组件的,在这个例子中 addedaddded_task 都可以。选一个能描述清楚发生的事件的名字!

    dispatch({// 针对特定的组件type: 'what_happened',// 其它字段放这里
    });
    
  2. 编写 一个 reducer 函数;

    reducer 函数就是你放置状态逻辑的地方。它接受两个参数,分别为当前 state 和 action 对象,并且返回的是更新后的 state:

    function yourReducer(state, action) {// 给 React 返回更新后的状态
    }
    

    在这个例子中,要将状态设置逻辑从事件处理程序移到 reducer 函数中,你需要:

    1. 声明当前状态(tasks)作为第一个参数;
    2. 声明 action 对象作为第二个参数;
    3. reducer 返回 下一个 状态(React 会将旧的状态设置为这个最新的状态)。
    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);}
    }
    

    上面语句用了if/else语句,但在reducers中使用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);}}
    }
    
  3. 在组件中 使用 reducer

事件处理程序只通过派发 action 来指定 发生了什么,而 reducer 函数通过响应 actions 来决定 状态如何更新

3.5.2 对比useState和useReducer

  • 代码体积:UI组件少可以用useState,组件复杂用useReducer
  • 可读性:依据状态更新逻辑复杂程度而定,简单时useState,复杂时useReducer
  • 可调试性:useReducer更佳,可以通过打印日志来调试
  • 可测试性:reducer函数是一个纯函数,可以单独进行测试

3.5.3 编写一个好的reducer函数

  • reducer必须是一个纯函数
  • 每个action都描述了一个单一的用户交互,即便它会引发多个数据的变化

3.5.4 使用Immer简化reducers

引入useImmerReducer:

import { useImmerReducer } from 'use-immer';

使用draft.函数修改state:

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);}}
}

3.6 使用Context深层传递参数

Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。

  1. 创建context

    // LevelContext.js
    import { createContext } from 'react';export const LevelContext = createContext(1);
  2. 使用context

    import { useContext } from 'react';
    import { LevelContext } from './LevelContext.js';export default function Heading({ children }) {const level = useContext(LevelContext);// ...
    }
    
  3. 提供context

    用 context provider 包裹起来 以提供 LevelContext 给它们:

    import { LevelContext } from './LevelContext.js';export default function Section({ level, children }) {return (<section className="section"><LevelContext.Provider value={level}>{children}</LevelContext.Provider></section>);
    }
    

Context 让你可以编写“适应周围环境”的组件,并且根据在哪 (或者说 在哪个 context 中)来渲染它们不同的样子。不同的 React context 不会覆盖彼此。Context 会穿过中间的任何组件。

3.6.1 context的使用场景

  • 主题: 如果你的应用允许用户更改其外观(例如暗夜模式),你可以在应用顶层放一个 context provider,并在需要调整其外观的组件中使用该 context。
  • 当前账户: 许多组件可能需要知道当前登录的用户信息。将它放到 context 中可以方便地在树中的任何位置读取它。
  • 路由: 大多数路由解决方案在其内部使用 context 来保存当前路由。
  • **状态管理:**通常 将 reducer context 搭配使用来管理复杂的状态并将其传递给深层的组件来避免过多的麻烦。

3.7 使用Reducer和Context拓展应用

步骤:

  1. 创建 context。
  2. 将 state 和 dispatch 放入 context。
  3. 在组件树的任何地方 使用 context。

为子组件提供 state 和 dispatch 函数:

  1. 创建两个 context (一个用于 state,一个用于 dispatch 函数)。

    import { createContext } from 'react';export const TasksContext = createContext(null);
    export const TasksDispatchContext = createContext(null);
    
  2. 让组件的 context 使用 reducer。

    export function TasksProvider({ children }) {const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);return (<TasksContext.Provider value={tasks}><TasksDispatchContext.Provider value={dispatch}>{children}</TasksDispatchContext.Provider></TasksContext.Provider>);
    }
    // 也可以从 TasksContext.js 中导出使用 context 的函数:
    export function useTasks() {return useContext(TasksContext);
    }export function useTasksDispatch() {return useContext(TasksDispatchContext);
    }
    
  3. 使用组件中需要读取的 context。

    // 组件可以通过以下函数读取 context:
    const tasks = useTasks();
    const dispatch = useTasksDispatch();
    

useTasksuseTasksDispatch 这样的函数被称为自定义 Hook, 如果你的函数名以 use 开头,它就被认为是一个自定义 Hook。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员工作技巧

提高工作技巧的秘方 案例 让我们猜测一下 绵羊想表达的什么&#xff1f; 并不够准确 崩了&#xff0c;不能用了整个业务瘫痪了研发没有责任感 语义规则/模棱俩可 相对语言 量化数据表达&#xff1a;疼苦指数&#xff0c;拥堵指数&#xff0c;准确。 尽量减少标签化评价 标签…

STM32的HAL库的定时器使用

用HAL库老是忘记了定时器中断怎么配置&#xff0c;该调用哪个回调函数。今天记录一下&#xff0c;下次再忘了就来翻一下。 系统的时钟配置&#xff0c;定时器的时钟是84MHz 这里定时器时钟是84M&#xff0c;分频是8400后&#xff0c;时基就是1/10000s&#xff0c;即0.1ms。Per…

嵌入式学习笔记(10)mkv210_image.c代码详解

第1步&#xff1a;检验用户传参是不是3个。 第2步&#xff1a;分配16KB buffer并且填充为0. 第3步&#xff1a;打开源bin&#xff08;led.bin&#xff09;&#xff0c;判断bin长度是否不大于16KB-16Bytes 第4步&#xff1a;以16个字符串填充0~15这16个Bytes 第5步&#xff…

Windows Network File System Remote Code Execution Vulnerability

文章目录 NFS(Network File System)漏洞描述攻击者如何利用此漏洞&#xff1f;该漏洞的危险程度机密性-high真实性-high可用性-high 如何降低漏洞风险推荐阅读 NFS(Network File System)漏洞描述 Name Microsoft Windows Network File System Remote Code Execution Vulnerabi…

verilator——牛刀小试

verilator——牛刀小试 安装verilator可见&#xff1a;https://blog.csdn.net/qq_40676869/article/details/132648522?spm1001.2014.3001.5501 正文开始 编写一个异或的电路模块如下&#xff1a; top.v module top(input a,input b,output f );assign f a ^ b; endmodul…

2.4 关系数据库

思维导图&#xff1a; 前言&#xff1a; 这段话描述了“关系数据库”及其背后的理论基础。首先&#xff0c;我们来拆分这段话并逐步解释每部分。 关系数据库是采用关系模型作为数据组织方式的数据库。 这句话的关键是“关系模型”。关系模型是一种表示和操作数据库的理论模型…

【GUI开发】用python爬YouTube博主信息,并开发成exe软件

文章目录 一、背景介绍二、代码讲解2.1 爬虫2.2 tkinter界面2.3 存日志 三、软件演示视频四、说明 一、背景介绍 你好&#xff0c;我是马哥python说&#xff0c;一名10年程序猿。 最近我用python开发了一个GUI桌面软件&#xff0c;目的是爬取相关YouTube博主的各种信息&#…

燃气管网监测系统,提升城市燃气安全防控能力

燃气是我们日常生活中不可或缺的能源&#xff0c;但其具有易燃易爆特性&#xff0c;燃气安全使用、泄漏监测尤为重要。当前全国燃气安全事故仍呈现多发频发态势&#xff0c;从公共安全的视角来看&#xff0c;燃气已成为城市安全的重大隐忧&#xff01;因此&#xff0c;建立一个…

结构化日志记录增强网络安全性

日志是一种宝贵的资产&#xff0c;在监视和分析应用程序或组织的 IT 基础结构的整体安全状况和性能方面发挥着至关重要的作用。它们提供系统事件、用户活动、网络流量和应用程序行为的详细记录&#xff0c;从而深入了解潜在威胁或未经授权的访问尝试。虽然组织历来依赖于传统的…

Ubuntu22.04安装Mongodb7.0

Ubuntu安装Mongodb 1.平台支持2.安装MongoDB社区版2.1导入包管理系统使用的公钥2.2为MongoDB创建列表文件2.3重新加载本地包数据库2.4安装MongoDB包1.安装最新版MongoDB2.安装指定版MongoDB 3.运行MongoDB社区版1.目录2.配置文件3.初始化系统4.启动MongoDB5.验证MongoDB是否成功…

【包过滤防火墙——iptables静态防火墙】的简单使用

文章目录 规则链的分类--五链处理的动作iptables常用参数和作用 防火墙就是堵和通的作用 iptables &#xff1a;包过滤防火墙&#xff0c;是内核防火墙netfilter的管理工具 核心&#xff1a;四表五链 规则链的分类–五链 在进行路由选择前处理的数据包&#xff1a;PREROUTIN…

C# Solidworks二次开发:创建距离配合以及移动组件API详解

今天要讲的文章是关于如何创建距离配合和移动组件的API详解。 &#xff08;1&#xff09;创建配合API&#xff0c;CreateMate() 这个API的解释是根据指定的特性数据对象来创建配合&#xff0c;也就可以理解为输入什么样的特征对象就可以创建出什么配合&#xff0c;这个API的输…

MAC M2芯片执行yolov8 + deepsort 实现目标跟踪

MAC M2芯片执行yolov8 deepsort 实现目标跟踪 MAC M2 YoloX bytetrack实现目标跟踪 实验结果 MAC mps显存太小了跑不动 还是得用服务器跑 需要实验室的服务器跑 因为网上花钱跑4天太贵了&#xff01;&#xff01;&#xff01; 步骤过程尝试&#xff1a; 执行mot17 数据集 …

网络协议分析-http/https/tcp/udp

文章目录 TCP三次握手/TCP三次挥手TCP三次握手TCP四次挥手完整报文 实例代码HttpSampleClientHttpSampleServerHttpsSampleClientHttpsSampleServerTcpSampleClientTcpSampleServerUdpSampleClientUdpSampleSever 资料 TCP三次握手/TCP三次挥手 “三次握手”的目的是“为了防止…

YOLO目标检测——视觉显著性检测MSRA1000数据集下载分享

MSRA1000数据集是一个常用的视觉显著性检测数据集&#xff0c;它包含了1000张图像和对应的显著性标注。在以下几个应用场景中&#xff0c;MSRA1000数据集可以发挥重要作用&#xff1a;图像编辑和后期处理、图像检索和分类、视觉注意力模型、自动驾驶和智能交通等等 数据集点击下…

鸿鹄企业工程项目管理系统 Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统源代码

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管…

内存管理方式

内存管理 一、C/C内存分布1、内存空间的介绍2、示例题目3、示例题目图解 二、C语言动态内存管理方式1、代码2、介绍 三、C内存管理方式1、概念2、代码3、代码所代表的意义 四、new和delete操作自定义类型1、代码2、运行结果3、特点 五、operator new与operator delete函数1、概…

IP网络广播系统有哪些优点

IP网络广播系统有哪些优点 IP网络广播系统有哪些优点&#xff1f; IP网络广播系统是基于 TCP/IP 协议的公共广播系统&#xff0c;采用 IP 局域网或 广域网作为数据传输平台&#xff0c;扩展了公共广播系统的应用范围。随着局域网络和 网络的发展 , 使网络广播的普及变为可能 …

【一等奖方案】大规模金融图数据中异常风险行为模式挖掘赛题「NUFE」解题思路

第十届CCF大数据与计算智能大赛&#xff08;2022 CCF BDCI&#xff09;已圆满结束&#xff0c;大赛官方竞赛平台DataFountain&#xff08;简称DF平台&#xff09;正在陆续释出各赛题获奖队伍的方案思路&#xff0c;欢迎广大数据科学家交流讨论。 本方案为【大规模金融图数据中…

Yao框架浏览器打开后Cannot read properties of undefined (reading ‘search‘)

Yao创建项目后浏览器打开报 undefined 错误&#xff0c;是因为 Yao 框架语言包的问题&#xff0c;只支持浏览器的【中文&#xff08;简体&#xff09;】【英语&#xff08;美国&#xff09;】&#xff0c;如果浏览器排第一的语言不是这两个就会出现这个错误。临时解决方案是在浏…