React 入门使用 (官方文档向 Part2)
文章目录
- 用 State 响应输入
- 声明式地考虑 UI
- 步骤 1:定位组件中不同的视图状态
- 步骤 2:确定是什么触发了这些状态的改变
- 步骤 3:通过 useState 表示内存中的 state
- 步骤 4:删除任何不必要的 state 变量
- 步骤 5:连接事件处理函数以设置 state
- 选择 State 结构
- 构建 state 的原则
- 在组件间共享状态
- 举例说明一下状态提升
- 第 1 步: 从子组件中移除状态
- 第 2 步: 从公共父组件传递硬编码数据
- 第 3 步: 为公共父组件添加状态
- 受控组件和非受控组件
- 对 state 进行保留和重置
- 状态与渲染树中的位置相关
- 相同位置的相同组件会使得 state 被保留下来
- 相同位置的不同组件会使 state 重置
- 在相同位置重置 state
- 方法一:将组件渲染在不同的位置
- 方法二:使用 key 来重置 state
- 为被移除的组件保留 state
- 迁移状态逻辑至 Reducer 中
- 使用 reducer 整合状态逻辑
- 第 1 步: 将设置状态的逻辑修改成 dispatch 的一个 action
- 第 2 步: 编写一个 reducer 函数
- 语法
- 示例
- 使用场景
- 注意事项
- 示例
- 第 3 步: 在组件中使用 reducer
- 对比 useState 和 useReducer
- 使用 Context 深层传递参数
- 传递 props 带来的问题
- Context:传递 props 的另一种方法
- 写在你使用 context 之前
- Context 的使用场景
- 使用 Reducer 和 Context 拓展你的应用
- 应急方案
- 使用 ref 引用值
- 给你的组件添加 ref
- ref 和 state 的不同之处
- useRef 内部是如何运行的?
- 何时使用 ref
- ref 的最佳实践
- 使用 ref 操作 DOM
- 获取指向节点的 ref
- 访问另一个组件的 DOM 节点
- 使用 Effect 同步
- 如何编写 Effect
- 第一步:声明 Effect
- 第二步:指定 Effect 依赖
- 第三部:按需添加清理(cleanup)函数
- 订阅事件
- 触发动画
- 初始化应用时不需要使用 Effect 的情形
- 你可能不需要 Effect
- 响应式 Effect 的生命周期
- Effect 的生命周期
[]以下内容来自官方文档 https://zh-hans.react.dev/learn](https://zh-hans.react.dev/learn)
用 State 响应输入
React 控制 UI 的方式是声明式的。你不必直接控制 UI 的各个部分,只需要声明组件可以处于的不同状态,并根据用户的输入在它们之间切换
声明式地考虑 UI
你已经从上面的例子看到如何去实现一个表单了,为了更好地理解如何在 React 中思考,接下来你将会学到如何用 React 重新实现这个 UI:
- 定位你的组件中不同的视图状态
- 确定是什么触发了这些 state 的改变
- 表示内存中的 state(需要使用 useState)
- 删除任何不必要的 state 变量
- 连接事件处理函数去设置 state
步骤 1:定位组件中不同的视图状态
在计算机科学中,你或许听过可处于多种“状态”之一的 “状态机”
首先,你需要去可视化 UI 界面中用户可能看到的所有不同的“状态”:
- 无数据:表单有一个不可用状态的“提交”按钮。
- 输入中:表单有一个可用状态的“提交”按钮。
- 提交中:表单完全处于不可用状态,加载动画出现。
- 成功时:显示“成功”的消息而非表单。
- 错误时:与输入状态类似,但会多错误的消息。
步骤 2:确定是什么触发了这些状态的改变
你可以触发 state 的更新来响应两种输入:
- 人为输入。比如点击按钮、在表单中输入内容,或导航到链接。
- 计算机输入。比如网络请求得到反馈、定时器被触发,或加载一张图片
你需要改变 state 以响应几个不同的输入:
- 改变输入框中的文本时(人为)应该根据输入框的内容是否是空值,从而决定将表单的状态从空值状态切换到输入中或切换回原状态。
- 点击提交按钮时(人为)应该将表单的状态切换到提交中的状态。
- 网络请求成功后(计算机)应该将表单的状态切换到成功的状态。
- 网络请求失败后(计算机)应该将表单的状态切换到失败的状态,与此同时,显示错误信息
步骤 3:通过 useState 表示内存中的 state
如果你很难立即想出最好的办法,那就先从添加足够多的 state 开始,确保所有可能的视图状态都囊括其中:
const [isEmpty, setIsEmpty] = useState(true);const [isTyping, setIsTyping] = useState(false);const [isSubmitting, setIsSubmitting] = useState(false);const [isSuccess, setIsSuccess] = useState(false);const [isError, setIsError] = useState(false);
你最初的想法或许不是最好的,但是没关系,重构 state 也是步骤中的一部分!
步骤 4:删除任何不必要的 state 变量
在清理之后,你只剩下 3 个(从原本的 7 个!)_必要_的 state 变量:
const [answer, setAnswer] = useState('');const [error, setError] = useState(null);const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'
步骤 5:连接事件处理函数以设置 state
练习1
import {useState} from 'react'
export default function Picture() {const [imgcss,setImgcss] = useState(false)function handleBgd(){setImgcss(false)}function handleImg(e){e.stopPropagation()setImgcss(true)}let bgdClassName = 'background'let imgClassName = 'picture'if(imgcss){imgClassName += ' picture--active' //注意此处的空格}else{bgdClassName += ' background--active' //注意此处的空格}return (<div className={bgdClassName} onClick={()=>setImgcss(false)}><imgonClick={e => {e.stopPropagation();setImgcss(true)}}className={imgClassName}alt="Rainbow houses in Kampung Pelangi, Indonesia"src="https://i.imgur.com/5qwVYb1.jpeg"/></div>);
}
练习2
import {useState} from 'react'
export default function EditProfile() {const [edit,setEdit]= useState(false)const [firstName,setFirstName] = useState('')const [lastName,setLastName] = useState('')return (<form onSubmit={(e)=>{e.preventDefault()setEdit(!edit)}}><label>First name:{' '}{edit? <input onChange={(e)=>{setFirstName(e.target.value)}} value={firstName}/>:<b>{firstName}</b>}</label><label>Last name:{' '}{edit? <input onChange={(e)=>{setLastName(e.target.value)}} value={lastName}/>:<b>{lastName}</b>}</label><button type="submit">{edit? 'save ': 'edit '} Profile</button><p><i>Hello, {firstName} {lastName}!</i></p></form>);
}
选择 State 结构
构建 state 的原则
当你编写一个存有 state 的组件时,你需要选择使用多少个 state 变量以及它们都是怎样的数据格式。尽管选择次优的 state 结构下也可以编写正确的程序,但有几个原则可以指导您做出更好的决策:
- 合并关联的 state。如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。
- 避免互相矛盾的 state。当 state 结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。
- 避免冗余的 state。如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。
- 避免重复的 state。当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。
- 避免深度嵌套的 state。深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state。
在组件间共享状态
有时候,你希望两个组件的状态始终同步更改。要实现这一点,可以将相关 state 从这两个组件上移除,并把 state 放到它们的公共父级,再通过 props 将 state 传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事
举例说明一下状态提升
在这个例子中,父组件 Accordion 渲染了 2 个独立的 Panel 组件。
- Accordion
- Panel
- Panel
每个 Panel 组件都有一个布尔值 isActive,用于控制其内容是否可见
import { useState } from 'react';function Panel({ title, children }) {const [isActive, setIsActive] = useState(false);return (<section className="panel"><h3>{title}</h3>{isActive ? (<p>{children}</p>) : (<button onClick={() => setIsActive(true)}>显示</button>)}</section>);
}export default function Accordion() {return (<><h2>哈萨克斯坦,阿拉木图</h2><Panel title="关于">阿拉木图人口约200万,是哈萨克斯坦最大的城市。它在 1929 年到 1997 年间都是首都。</Panel><Panel title="词源">这个名字来自于 <span lang="kk-KZ">алма</span>,哈萨克语中“苹果”的意思,经常被翻译成“苹果之乡”。事实上,阿拉木图的周边地区被认为是苹果的发源地,<i lang="la">Malus sieversii</i> 被认为是现今苹果的祖先。</Panel></>);
}
假设现在您想改变这种行为,以便在任何时候只展开一个面板。在这种设计下,展开第 2 个面板应会折叠第 1 个面板。您该如何做到这一点呢?”
要协调好这两个面板,我们需要分 3 步将状态“提升”到他们的父组件中。
- 从子组件中 移除 state 。
- 从父组件 传递 硬编码数据。
- 为共同的父组件添加 state ,并将其与事件处理函数一起向下传递
第 1 步: 从子组件中移除状态
你将把 Panel 组件对 isActive 的控制权交给他们的父组件。这意味着,父组件会将 isActive 作为 prop 传给子组件 Panel。我们先从 Panel 组件中 删除下面这一行:
const [isActive, setIsActive] = useState(false);
然后,把 isActive 加入 Panel 组件的 props 中:
function Panel({ title, children, isActive }) {
第 2 步: 从公共父组件传递硬编码数据
import { useState } from 'react';export default function Accordion() {return (<><h2>哈萨克斯坦,阿拉木图</h2><Panel title="关于" isActive={true}>阿拉木图人口约200万,是哈萨克斯坦最大的城市。它在 1929 年到 1997 年间都是首都。</Panel><Panel title="词源" isActive={true}>这个名字来自于 <span lang="kk-KZ">алма</span>,哈萨克语中“苹果”的意思,经常被翻译成“苹果之乡”。事实上,阿拉木图的周边地区被认为是苹果的发源地,<i lang="la">Malus sieversii</i> 被认为是现今苹果的祖先。</Panel></>);
}function Panel({ title, children, isActive }) {return (<section className="panel"><h3>{title}</h3>{isActive ? (<p>{children}</p>) : (<button onClick={() => setIsActive(true)}>显示</button>)}</section>);
}
第 3 步: 为公共父组件添加状态
状态提升通常会改变原状态的数据存储类型。
在这个例子中,一次只能激活一个面板。这意味着 Accordion 这个父组件需要记录 哪个 面板是被激活的面板。我们可以用数字作为当前被激活 Panel 的索引,而不是 boolean 值:
const [activeIndex, setActiveIndex] = useState(0);
import { useState } from 'react';export default function Accordion() {const [activeIndex, setActiveIndex] = useState(0);return (<><h2>哈萨克斯坦,阿拉木图</h2><Paneltitle="关于"isActive={activeIndex === 0}onShow={() => setActiveIndex(0)}>阿拉木图人口约200万,是哈萨克斯坦最大的城市。它在 1929 年到 1997 年间都是首都。</Panel><Paneltitle="词源"isActive={activeIndex === 1}onShow={() => setActiveIndex(1)}>这个名字来自于 <span lang="kk-KZ">алма</span>,哈萨克语中“苹果”的意思,经常被翻译成“苹果之乡”。事实上,阿拉木图的周边地区被认为是苹果的发源地,<i lang="la">Malus sieversii</i> 被认为是现今苹果的祖先。</Panel></>);
}function Panel({title,children,isActive,onShow
}) {return (<section className="panel"><h3>{title}</h3>{isActive ? (<p>{children}</p>) : (<button onClick={onShow}>显示</button>)}</section>);
}
受控组件和非受控组件
通常我们把包含“不受控制”状态的组件称为“非受控组件”。例如,最开始带有 isActive 状态变量的 Panel 组件就是不受控制的,因为其父组件无法控制面板的激活状态。
相反,当组件中的重要信息是由 props 而不是其自身状态驱动时,就可以认为该组件是“受控组件”。这就允许父组件完全指定其行为。最后带有 isActive 属性的 Panel 组件是由 Accordion 组件控制的
练习1
import { useState } from 'react';export default function SyncedInputs() {const [text,setText]= useState('')function handleChange(e){setText(e.target.value)}return (<><Input label="第一个输入框" onChange={handleChange} text={text}/><Input label="第二个输入框" onChange={handleChange} text={text}/></>);
}function Input({ label, onChange, text }) {//const [text, setText] = useState('');/*function handleChange(e) {setText(e.target.value);}*/return (<label>{label}{' '}<inputvalue={text}onChange={onChange}/></label>);
}
练习2
import { useState } from 'react';
import { foods, filterItems } from './data.js';export default function FilterableList() {const [query,setQuery] = useState('')function handleQuery(e){setQuery(e.target.value)}return (<><SearchBar text={query} onChange={handleQuery}/><hr /><List items={foods} text={query}/></>);
}function SearchBar({text,onChange}) {/*const [query, setQuery] = useState('');function handleChange(e) {setQuery(e.target.value);}*/return (<label>搜索:{' '}<inputvalue={text}onChange={onChange}/></label>);
}function List({ items,text }) {return (<table><tbody>{filterItems(items,text).map(food => (<tr key={food.id}><td>{food.name}</td><td>{food.description}</td></tr>))}</tbody></table>);
}
对 state 进行保留和重置
状态与渲染树中的位置相关
只有当在树中相同的位置渲染相同的组件时,React 才会一直保留着组件的 state
import { useState } from 'react';export default function App() {const [showB, setShowB] = useState(true);return (<div><Counter />{showB && <Counter />} <label><inputtype="checkbox"checked={showB}onChange={e => {setShowB(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>);
}
注意,当你停止渲染第二个计数器的那一刻,它的 state 完全消失了。这是因为 React 在移除一个组件时,也会销毁它的 state
当你重新勾选“渲染第二个计数器”复选框时,另一个计数器及其 state 将从头开始初始化(score = 0)并被添加到 DOM 中。
相同位置的相同组件会使得 state 被保留下来
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>);
}
相同位置的不同组件会使 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>);
}
并且,当你在相同位置渲染不同的组件时,组件的整个子树都会被重置。要验证这一点,可以增加计数器的值然后勾选复选框
import { useState } from 'react';export default function App() {const [isFancy, setIsFancy] = useState(false);return (<div>{isFancy ? (<div><Counter isFancy={true} /> </div>) : (<section><Counter isFancy={false} /></section>)}<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>);
}
在相同位置重置 state
方法一:将组件渲染在不同的位置
import { useState } from 'react';export default function Scoreboard() {const [isPlayerA, setIsPlayerA] = useState(true);return (<div>{isPlayerA &&<Counter person="Taylor" />}{!isPlayerA &&<Counter person="Sarah" />}<button onClick={() => {setIsPlayerA(!isPlayerA);}}>下一位玩家!</button></div>);
}function Counter({ person }) {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>{person} 的分数:{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}
方法二:使用 key 来重置 state
key 不只可以用于列表!你可以使用 key 来让 React 区分任何组件。默认情况下,React 使用父组件内部的顺序(“第一个计数器”、“第二个计数器”)来区分组件。但是 key 可以让你告诉 React 这不仅仅是 第一个 或者 第二个 计数器,而且还是一个特定的计数器——例如,Taylor 的 计数器。这样无论它出现在树的任何位置, React 都会知道它是 Taylor 的 计数器!
import { useState } from 'react';export default function Scoreboard() {const [isPlayerA, setIsPlayerA] = useState(true);return (<div>{isPlayerA ? (<Counter key="Taylor" person="Taylor" />) : (<Counter key="Sarah" person="Sarah" />)}<button onClick={() => {setIsPlayerA(!isPlayerA);}}>下一位玩家!</button></div>);
}function Counter({ person }) {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>{person} 的分数:{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}
为被移除的组件保留 state
在真正的聊天应用中,你可能会想在用户再次选择前一个收件人时恢复输入 state。对于一个不可见的组件,有几种方法可以让它的 state “活下去”:
- 与其只渲染现在这一个聊天,你可以把 所有 聊天都渲染出来,但用 CSS 把其他聊天隐藏起来。这些聊天就不会从树中被移除了,所以它们的内部 state 会被保留下来。这种解决方法对于简单 UI 非常有效。但如果要隐藏的树形结构很大且包含了大量的 DOM 节点,那么性能就会变得很差。
- 你可以进行 状态提升 并在父组件中保存每个收件人的草稿消息。这样即使子组件被移除了也无所谓,因为保留重要信息的是父组件。这是最常见的解决方法。
- 除了 React 的 state,你也可以使用其他数据源。例如,也许你希望即使用户不小心关闭页面也可以保存一份信息草稿。要实现这一点,你可以让 Chat 组件通过读取 localStorage 对其 state 进行初始化,并把草稿保存在那里。
练习1
import { useState } from 'react';export default function App() {const [showHint, setShowHint] = useState(false);if (showHint) {return (<div><p><i>提示:你最喜欢的城市?</i></p><Form /><button onClick={() => {setShowHint(false);}}>隐藏提示</button></div>);}return (<div><p></p><Form /><button onClick={() => {setShowHint(true);}}>显示提示</button></div>);
}function Form() {const [text, setText] = useState('');return (<textareavalue={text}onChange={e => setText(e.target.value)}/>);
}
练习2
import { useState } from 'react';export default function App() {const [reverse, setReverse] = useState(false);let checkbox = (<label><inputtype="checkbox"checked={reverse}onChange={e => setReverse(e.target.checked)}/>调换顺序</label>);if (reverse) {return (<><Field label="姓氏" key='姓氏' /> <Field label="名字" key='名字'/>{checkbox}</>);} else {return (<><Field label="名字" key='名字'/> <Field label="姓氏" key='姓氏'/>{checkbox}</>); }
}function Field({ label }) {const [text, setText] = useState('');return (<label>{label}:<inputtype="text"value={text}placeholder={label}onChange={e => setText(e.target.value)}/></label>);
}
练习3,4,5
迁移状态逻辑至 Reducer 中
使用 reducer 整合状态逻辑
随着组件复杂度的增加,你将很难一眼看清所有的组件状态更新逻辑。例如,下面的 TaskApp 组件有一个数组类型的状态 tasks,并通过三个不同的事件处理程序来实现任务的添加、删除和修改
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} /><TaskListtasks={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},
];
你可以通过三个步骤将 useState 迁移到 useReducer
第 1 步: 将设置状态的逻辑修改成 dispatch 的一个 action
移除所有的状态设置逻辑。只留下三个事件处理函数:
- handleAddTask(text) 在用户点击 “添加” 时被调用。
- handleChangeTask(task) 在用户切换任务或点击 “保存” 时被调用。
- handleDeleteTask(taskId) 在用户点击 “删除” 时被调用。
使用 reducers 管理状态与直接设置状态略有不同。它不是通过设置状态来告诉 React “要做什么”,而是通过事件处理程序 dispatch 一个 “action” 来指明 “用户刚刚做了什么”。(而状态更新逻辑则保存在其他地方!)因此,我们不再通过事件处理器直接 “设置 task”,而是 dispatch 一个 “添加/修改/删除任务” 的 action。这更加符合用户的思维。
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,});
}
第 2 步: 编写一个 reducer 函数
reducer 函数就是你放置状态逻辑的地方。它接受两个参数,分别为当前 state 和 action 对象,并且返回的是更新后的 state
在这个例子中,要将状态设置逻辑从事件处理程序移到 reducer 函数中,你需要:
- 声明当前状态(tasks)作为第一个参数;
- 声明 action 对象作为第二个参数;
- 从 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 语句 是一种惯例。两种方式结果是相同的,但 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);}}
}
Reduce()reduce()
是 JavaScript 中数组的高阶函数之一,用于将数组中的元素归纳为单个值。它接受一个回调函数作为参数,这个回调函数可以进行累积操作。
语法
array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue);
callback
:用于处理数组中每个元素的回调函数,包含四个参数:accumulator
:累积器,累积计算的结果。currentValue
:当前处理的元素。currentIndex
(可选):当前处理的元素的索引。array
(可选):调用reduce
的数组。
initialValue
(可选):作为第一次调用callback
时的第一个参数accumulator
的初始值。如果不提供,将使用数组的第一个元素作为初始值,且callback
不会在数组的第一个元素上调用。
示例
// 计算数组元素的和
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 输出 15// 找出数组中的最大值
const max = numbers.reduce((accumulator, currentValue) => Math.max(accumulator, currentValue));
console.log(max); // 输出 5
使用场景
- 累加或累乘:计算数组中所有元素的和或积。
- 查找最值:找出数组中的最大值或最小值。
- 转换数据格式:将数组转换成其他数据结构。
- 归纳操作:进行复杂的归纳计算。
注意事项
- 在没有提供
initialValue
的情况下,reduce
将从数组的第二个元素开始调用回调函数。 - 如果数组为空且没有提供
initialValue
,reduce
将抛出TypeError
。 - 回调函数在数组有元素时至少会被调用一次。
reduce
不会改变原数组。
示例
// 使用 reduce 计算数组中的平均值
const numbers = [10, 20, 30, 40, 50];
const average = numbers.reduce((accumulator, currentValue, index, array) => {accumulator += currentValue;if (index === array.length - 1) {return accumulator / array.length;} else {return accumulator;}
}, 0);console.log(average); // 输出 30
在这个示例中,reduce
被用于计算数组 numbers
中所有元素的平均值。
第 3 步: 在组件中使用 reducer
你需要将 tasksReducer 导入到组件中。记得先从 React 中导入 useReducer Hook:
import { useReducer } from 'react';
接下来,你就可以替换掉之前的 useState:
const [tasks, setTasks] = useState(initialTasks);
只需要像下面这样使用 useReducer:
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
useReducer 和 useState 很相似——你必须给它传递一个初始状态,它会返回一个有状态的值和一个设置该状态的函数(在这个例子中就是 dispatch 函数)。但是,它们两个之间还是有点差异的。
useReducer 钩子接受 2 个参数:
- 一个 reducer 函数
- 一个初始的 state
它返回如下内容:
- 一个有状态的值
- 一个 dispatch 函数(用来 “派发” 用户操作给 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} /><TaskListtasks={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);}}
}let nextId = 3;
const initialTasks = [{id: 0, text: '参观卡夫卡博物馆', done: true},{id: 1, text: '看木偶戏', done: false},{id: 2, text: '打卡列侬墙', done: false}
];
对比 useState 和 useReducer
- 代码体积: 通常,在使用 useState 时,一开始只需要编写少量代码。而 useReducer 必须提前编写 reducer 函数和需要调度的 actions。但是,当多个事件处理程序以相似的方式修改 state 时,useReducer 可以减少代码量。
- 可读性: 当状态更新逻辑足够简单时,useState 的可读性还行。但是,一旦逻辑变得复杂起来,它们会使组件变得臃肿且难以阅读。在这种情况下,useReducer 允许你将状态更新逻辑与事件处理程序分离开来。
- 可调试性: 当使用 useState 出现问题时, 你很难发现具体原因以及为什么。 而使用 useReducer 时, 你可以在 reducer 函数中通过打印日志的方式来观察每个状态的更新,以及为什么要更新(来自哪个 action)。 如果所有 action 都没问题,你就知道问题出在了 reducer 本身的逻辑中。 然而,与使用 useState 相比,你必须单步执行更多的代码。
- 可测试性: reducer 是一个不依赖于组件的纯函数。这就意味着你可以单独对它进行测试。一般来说,我们最好是在真实环境中测试组件,但对于复杂的状态更新逻辑,针对特定的初始状态和 action,断言 reducer 返回的特定状态会很有帮助。
- 个人偏好: 并不是所有人都喜欢用 reducer,没关系,这是个人偏好问题。你可以随时在 useState 和 useReducer 之间切换,它们能做的事情是一样的
练习1,2,3,4 暂时搁置
使用 Context 深层传递参数
通常来说,你会通过 props 将信息从父组件传递到子组件。但是,如果你必须通过许多中间组件向下传递 props,或是在你应用中的许多组件需要相同的信息,传递 props 会变的十分冗长和不便。Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递
传递 props 带来的问题
Context:传递 props 的另一种方法
Context 让父组件可以为它下面的整个组件树提供数据
你可以通过以下三个步骤来实现它:
- 创建 一个 context。(你可以将其命名为 LevelContext, 因为它表示的是标题级别。)
- 在需要数据的组件内 使用 刚刚创建的 context。(Heading 将会使用 LevelContext。)
- 在指定数据的组件中 提供 这个 context。 (Section 将会提供 LevelContext。
写在你使用 context 之前
使用 Context 看起来非常诱人!然而,这也意味着它也太容易被过度使用了。如果你只想把一些 props 传递到多个层级中,这并不意味着你需要把这些信息放到 context 里
在使用 context 之前,你可以考虑以下几种替代方案:
- 从 传递 props 开始。 如果你的组件看起来不起眼,那么通过十几个组件向下传递一堆 props 并不罕见。这有点像是在埋头苦干,但是这样做可以让哪些组件用了哪些数据变得十分清晰!维护你代码的人会很高兴你用 props 让数据流变得更加清晰。
- 抽象组件并 将 JSX 作为children传递 给它们。 如果你通过很多层不使用该数据的中间组件(并且只会向下传递)来传递数据,这通常意味着你在此过程中忘记了抽象组件。举个例子,你可能想传递一些像 posts 的数据 props 到不会直接使用这个参数的组件,类似 。取而代之的是,让 Layout 把 children 当做一个参数,然后渲染 。这样就减少了定义数据的组件和使用数据的组件之间的层级
Context 的使用场景
- 主题: 如果你的应用允许用户更改其外观(例如暗夜模式),你可以在应用顶层放一个 context provider,并在需要调整其外观的组件中使用该 context。
- 当前账户: 许多组件可能需要知道当前登录的用户信息。将它放到 context 中可以方便地在树中的任何位置读取它。某些应用还允许你同时操作多个账户(例如,以不同用户的身份发表评论)。在这些情况下,将 UI 的一部分包裹到具有不同账户数据的 provider 中会很方便。
- 路由: 大多数路由解决方案在其内部使用 context 来保存当前路由。这就是每个链接“知道”它是否处于活动状态的方式。如果你创建自己的路由库,你可能也会这么做。
- 状态管理: 随着你的应用的增长,最终在靠近应用顶部的位置可能会有很多 state。许多遥远的下层组件可能想要修改它们。通常 将 reducer 与 context 搭配使用来管理复杂的状态并将其传递给深层的组件来避免过多的麻烦
传递 Context 的方法:
- 通过 export const MyContext = createContext(defaultValue) 创建并导出 context。
- 在无论层级多深的任何子组件中,把 context 传递给 useContext(MyContext) Hook 来读取它。
- 在父组件中把 children 包在 <MyContext.Provider value={…}> 中来提供 context。
使用 Reducer 和 Context 拓展你的应用
下面将介绍如何结合使用 reducer 和 context:
- 创建 context。
- 将 state 和 dispatch 放入 context。
- 在组件树的任何地方 使用 context
应急方案
使用 ref 引用值
当你希望组件“记住”某些信息,但又不想让这些信息 触发新的渲染 时,你可以使用 ref 。
给你的组件添加 ref
你可以通过从 React 导入 useRef Hook 来为你的组件添加一个 ref:
import { useRef } from 'react';
在你的组件内,调用 useRef Hook 并传入你想要引用的初始值作为唯一参数。例如,这里的 ref 引用的值是“0”:
const ref = useRef(0);
useRef 返回一个这样的对象:
{ current: 0 // 你向 useRef 传入的值}
import { useRef } from 'react';export default function Counter() {let ref = useRef(0);function handleClick() {ref.current = ref.current + 1;alert('你点击了 ' + ref.current + ' 次!');}return (<button onClick={handleClick}>点击我!</button>);
}
组件不会在每次递增时重新渲染。 与 state 一样,React 会在每次重新渲染之间保留 ref。但是,设置 state 会重新渲染组件,更改 ref 不会!
ref 和 state 的不同之处
useRef 内部是如何运行的?
尽管 useState 和 useRef 都是由 React 提供的,原则上 useRef 可以在 useState的基础上 实现。 你可以想象在 React 内部,useRef 是这样实现的:
// React 内部function useRef(initialValue) {const [ref, unused] = useState({ current: initialValue });return ref;}
何时使用 ref
- 存储 timeout ID
- 存储和操作 DOM 元素,我们将在 下一页 中介绍
- 存储不需要被用来计算 JSX 的其他对象
ref 的最佳实践
遵循这些原则将使你的组件更具可预测性:
- 将 ref 视为应急方案。 当你使用外部系统或浏览器 API 时,ref 很有用。如果你很大一部分应用程序逻辑和数据流都依赖于 ref,你可能需要重新考虑你的方法。
- 不要在渲染过程中读取或写入 ref.current。 如果渲染过程中需要某些信息,请使用 state 代替。由于 React 不知道 ref.current 何时发生变化,即使在渲染时读取它也会使组件的行为难以预测。(唯一的例外是像 if (!ref.current) ref.current = new Thing() 这样的代码,它只在第一次渲染期间设置一次 ref。)
使用 ref 操作 DOM
获取指向节点的 ref
要访问由 React 管理的 DOM 节点,首先,引入 useRef Hook:
import { useRef } from 'react';
然后,在你的组件中使用它声明一个 ref:
const myRef = useRef(null);
Finally, pass your ref as the ref attribute to the JSX tag for which you want to get the DOM node:
<div ref={myRef}>
useRef Hook 返回一个对象,该对象有一个名为 current 的属性。最初,myRef.current 是 null。当 React 为这个
// 你可以使用任意浏览器 API,例如:myRef.current.scrollIntoView();
在上面的例子中,ref 的数量是预先确定的。但有时候,你可能需要为列表中的每一项都绑定 ref ,而你又不知道会有多少项。像下面这样做是行不通的:
<ul>{items.map((item) => {// 行不通!const ref = useRef(null);return <li ref={ref} />;})}</ul>
这是因为 Hook 只能在组件的顶层被调用。不能在循环语句、条件语句或 map() 函数中调用 useRef
访问另一个组件的 DOM 节点
默认情况下,React 不允许组件访问其他组件的 DOM 节点。甚至自己的子组件也不行
想要 暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件。下面是 MyInput 如何使用 forwardRef API:
const MyInput = forwardRef((props, ref) => {return <input {...props} ref={ref} />;});
使用 Effect 同步
有些组件需要与外部系统同步。例如,你可能希望根据 React state 控制非 React 组件、设置服务器连接或在组件出现在屏幕上时发送分析日志。Effects 会在渲染后运行一些代码,以便可以将组件与 React 之外的某些系统同步
在本文和后续文本中,Effect 在 React 中是专有定义——由渲染引起的副作用。为了指代更广泛的编程概念,也可以将其称为“副作用(side effect)
不要随意在你的组件中使用 Effect。记住,Effect 通常用于暂时“跳出” React 代码并与一些 外部 系统进行同步。这包括浏览器 API、第三方小部件,以及网络等等。如果你想用 Effect 仅根据其他状态调整某些状态,那么 你可能不需要 Effect。
如何编写 Effect
第一步:声明 Effect
首先在 React 中引入 useEffect Hook:
import { useEffect } from 'react';
然后,在组件顶部调用它,并传入在每次渲染时都需要执行的代码:
function MyComponent() {useEffect(() => {// 每次渲染后都会执行此处的代码});return <div />;}
一般来说,Effect 会在 每次 渲染后执行,而以下代码会陷入死循环中:
const [count, setCount] = useState(0);useEffect(() => {setCount(count + 1);});
每次渲染结束都会执行 Effect;而更新 state 会触发重新渲染。但是新一轮渲染时又会再次执行 Effect,然后 Effect 再次更新 state……如此周而复始,从而陷入死循环
第二步:指定 Effect 依赖
一般来说,Effect 会在 每次 渲染时执行。但更多时候,并不需要每次渲染的时候都执行 Effect。
- 有时这会拖慢运行速度。因为与外部系统的同步操作总是有一定时耗,在非必要时可能希望跳过它。例如,没有人会希望每次用键盘打字时都重新连接聊天服务器。
- 有时这会导致程序逻辑错误。例如,组件的淡入动画只需要在第一轮渲染出现时播放一次,而不是每次触发新一轮渲染后都播放。
useEffect(() => {if (isPlaying) { // isPlaying 在此处使用……// ...} else {// ...}}, [isPlaying]); // ……所以它必须在此处声明!
指定 [isPlaying] 会告诉 React,如果 isPlaying 在上一次渲染时与当前相同,它应该跳过重新运行 Effect
没有依赖数组作为第二个参数,与依赖数组位空数组 [] 的行为是不一致的:
useEffect(() => {// 这里的代码会在每次渲染后执行});useEffect(() => {// 这里的代码只会在组件挂载后执行}, []);useEffect(() => {//这里的代码只会在每次渲染后,并且 a 或 b 的值与上次渲染不一致时执行}, [a, b]);
第三部:按需添加清理(cleanup)函数
可以在 Effect 中返回一个 清理(cleanup) 函数。useEffect(() => {const connection = createConnection();connection.connect();return () => {connection.disconnect();};}, []);
每次重新执行 Effect 之前,React 都会调用清理函数;组件被卸载时,也会调用清理函数
订阅事件
如果 Effect 订阅了某些事件,清理函数应该退订这些事件:
useEffect(() => {function handleScroll(e) {console.log(window.scrollX, window.scrollY);}window.addEventListener('scroll', handleScroll);return () => window.removeEventListener('scroll', handleScroll);}, []);
触发动画
如果 Effect 对某些内容加入了动画,清理函数应将动画重置:
useEffect(() => {const node = ref.current;node.style.opacity = 1; // 触发动画return () => {node.style.opacity = 0; // 重置为初始值};}, []);
初始化应用时不需要使用 Effect 的情形
某些逻辑应该只在应用程序启动时运行一次。比如,验证登陆状态和加载本地程序数据。你可以将其放在组件之外:
if (typeof window !== 'undefined') { // 检查是否在浏览器中运行checkAuthToken();loadDataFromLocalStorage();}function App() {// ……}
你可能不需要 Effect
Effect 是 React 范式中的一个逃脱方案。它们让你可以 “逃出” React 并使组件和一些外部系统同步,比如非 React 组件、网络和浏览器 DOM。如果没有涉及到外部系统(例如,你想根据 props 或 state 的变化来更新一个组件的 state),你就不应该使用 Effect
响应式 Effect 的生命周期
Effect 的生命周期
每个 React 组件都经历相同的生命周期:
- 当组件被添加到屏幕上时,它会进行组件的 挂载。
- 当组件接收到新的 props 或 state 时,通常是作为对交互的响应,它会进行组件的 更新。
- 当组件从屏幕上移除时,它会进行组件的 卸载
相关文章:

React 入门使用 (官方文档向 Part2)
文章目录 用 State 响应输入声明式地考虑 UI步骤 1:定位组件中不同的视图状态步骤 2:确定是什么触发了这些状态的改变步骤 3:通过 useState 表示内存中的 state步骤 4:删除任何不必要的 state 变量步骤 5:连接事件处理…...
vue运用之el-cascader组件
前言 el-cascader 是 Element UI 的级联选择器组件。以下是一些常见的 el-cascader 问题以及对应的案例代码。 1. 如何使用 el-cascader 创建一个级联选择器 以下是一个简单的 el-cascader 示例: <template> <el-cascader v-model="selected" :option…...

layui提示框没有渲染bug解决
bug:使用layui时或许是依赖导入又或是ideal和浏览器缓存问题导致前面明明正常的页面显示,后面出现提示框没有css样式,弹出框没有背景css 效果如下 解决后 解决方法 在你的代码中引入layer.js 我这是jsp页面 <script type"text/jav…...
MATLAB和S7-1200PLC水箱液位高度PID控制联合仿真(MODBUSTCP通信)
MATLAB和SMART 200PLC的联合仿真请查看下面文章链接 MATLAB Simulink和SMART PLC水箱液位高度PID控制(联合仿真)-CSDN博客文章浏览阅读606次。SMART PLC 向导PID的详细介绍请查看下面文章链接:S7-200 SMART PLC PID向导详细介绍(如何实现P、PD、PID控制器)-CSDN博客文章浏览阅…...

QT 项目中添加文件夹(分类文件)
为了更方便的整理项目的文件,添加文件夹把文件进行分类。 1.首先在项目文件中创建新的文件夹 2.把需要归类的文件放入新建的文件中 3.右键然后选择add..... 4.运行此程序,会报错因为文件路径改变了,需要在.pro中修改路径 注意事项 文件夹内部…...
vue3 语音播报流程
npm 安装 "speak-tts": "^2.0.8", npm install speak-tts 在vue文件中引用 import Speech from "speak-tts"; const speech ref(null);onMounted(() > {speechInit(); });//语音播报初始化 const speechInit () > {speech.value ne…...

Linux MTR(My TraceRoute)command
Internet上有许多小型网络测试工具:Ping、Traceroute、Dig、Host等。 但是,这些工具的功能都比较单一。今天会给大家分享一个包含ping和traceroute功能的工具:MTR 文章目录 什么是MTR?MTR可以提供哪些功能Linux MTR可用选项Linux MTR用法推荐…...
第十一章 python基础之api
Python基础、函数、模块、面向对象、网络和并发编程、数据库和缓存、 前端、django、Flask、tornado、api、git、爬虫、算法和数据结构、Linux、设计题、客观题、其他 第十一章 api 1. 什么是webservice? Web服务(Web Services)是一种通过网…...

redis运维(十六) 有序集合
一 有序集合 把握一点: 各种redis 命令都提供各种语言对应的API 接口,后续API是关键 ① 概念 1、sorted set --> 有序集合2、redis有序集合也是集合类型的一部分,所以它保留了集合中元素不能重复的特性3、但是不同的是,有序集合给每个元素多设置…...
深入理解RC4加密算法
RC4(Rivest Cipher 4)是一种广泛应用的加密算法,由Ronald L. Rivest于1987年发明。它是一种流密码(stream cipher)算法,适用于对网络通信中的数据进行加密保护。 RC4加密解密 -- 一个覆盖广泛主题工具的高…...
sql24(Leetcode1141查询近30天活跃用户数)
代码: # Write your MySQL query statement belowselect v.activity_date as day, count(distinct(v.user_id)) as active_users from(select user_id,activity_datefrom Activitywhere activity_date between 2019-06-28 and 2019-07-27 ) as v group by v.activi…...

python爬取robomaster论坛数据,作为后端数据
一. 内容简介 python爬取robomaster论坛数据,作为后端数据 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3代码 三.主要流程 3.1 接口分析 # 接口分析 # 全部数据 # https://bbs.robomaster.com/forum.php?modforumdisplay&fid63 2…...

C++: string的模拟实现
C: string的模拟实现 一.前置说明1.模拟实现string容器的目的2.我们要实现的大致框架 二.默认成员函数1.构造函数2.拷贝构造函数1.传统写法2.现代写法 3.析构函数4.赋值运算符重载1.传统写法2.现代写法 三.遍历和访问1.operator[]运算符重载2.iterator迭代器 四.容量相关函数1.…...

[安洵杯 2019]easy_web
打开环境 img传参还有cmd img应该是base,先解码看看 3535352e706e67 这个好像是十六进制的,再解 访问一下看看,得到一张图片 尝试base解码,但是没有什么发现 再看看地址栏出现index.php,应该是要下载源码,但是还没有…...
CentOS7 安装配置SFTP服务器详解
1、SFTP简介 SSH文件传输协议(英语:SSH File Transfer Protocol,也称Secret File Transfer Protocol,中文:安全文件传送协议,英文:Secure FTP或字母缩写:SFTP)是一种数据流连接,提供文件访问、传输和管理功能的...

【Linux】Shell命令以及运行原理
目录 一、Linux是什么 二、Shell 三、为什么要有Shell 四、Shell的工作原理 一、Linux是什么 狭义上的Linux是指Linux内核本身,它是操作系统的核心部分,负责管理计算机的硬件资源(如处理器、内存、设备等),提供基…...
vue-动态组件、keep-alive
vue-动态组件、keep-alive 如果我们想写一个tabbar导航栏,我能想到的两种方式 通过if条件判断的方式实现(不赘述)动态组件 接下来我们就看看动态组件如何创建,废话不多少直接上代码(代码中有备注) 首先…...
华为OD机试 - 执行任务赚积分(Java JS Python C)
题目描述 现有N个任务需要处理,同一时间只能处理一个任务,处理每个任务所需要的时间固定为1。 每个任务都有最晚处理时间限制和积分值,在最晚处理时间点之前处理完成任务才可获得对应的积分奖励。 可用于处理任务的时间有限,请问在有限的时间内,可获得的最多积分。 输入…...

如何用CHAT配置linux的远程连接?
问CHAT:配置linux的远程连接 1.下载ssh 2.启动ssh服务 3.查看ssh服务状态 4.设置ssh服务开机自启动 5.设置windows的cmd下ssh 6.通过cmd的ssh命令远程到linux linux的ip:10.8.9.23 用户名:Li CHAT回复:以下是为配置Linux的远程连接的步骤说明:…...

Python (十六) 错误和异常
程序员的公众号:源1024,获取更多资料,无加密无套路! 最近整理了一波电子书籍资料,包含《Effective Java中文版 第2版》《深入JAVA虚拟机》,《重构改善既有代码设计》,《MySQL高性能-第3版》&…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...