篇首语:本文由编程笔记#自由互联小编为大家整理,主要介绍了这个知识点,是 React 的命脉相关的知识,希望对你有一定的参考价值。
创建组件时我们可以通过 props 接收外部传入的数据该数据可以称之为组件外部数据。除此之外React还有一个命脉知识点 -> 组件内部数据state.
使用函数创建组件时内部数据 state 通过 useState 定义。
function Coutner() // 利用数组结构得到两个变量 // count表示定义的数据 // setCount修改该数据的方法 // useState从闭包数据中取出 count 的值0 仅表示默认值 const [count, setCount] useState(0) return ( count )
由于目前函数组件足以支撑所有场景的实现因此写法更为复杂的 class 组件相关知识本系列将不会涉及有兴趣的同学可以阅读官网了解。现在主流的项目也基本全面采用函数式组件相关解决方案。只有部分落后的项目团队依然在坚持 class 组件。React 提供了方便平滑的升级模式还在维护老项目的同学可以跟着本系列学习函数组件并逐步重构项目
state 属于被监控的数据它是 React 实现数据驱动 UI 的核心。当 state 值发生变化时组件会尝试重新渲染因此函数会重新执行一次。函数重新执行后此时 count 的数据已经是变化后的结果因此渲染到 UI 的结果也会发生变化。
import useState from react;export default function Counter() const [count, setCount] useState(0) return ( count setCount(count 1)>递增 )
我在带学生的过程中遇到一个非常有趣的现象我期望 count 的值能递增于是这样写 setCount(count)你们猜 count 会按照预想的结果发生变化吗
在 TypeScript 中使用 useState 时我们应该使用如下的方式声明 state 的数据类型
const [count, setCount] useState(0);
但是通常情况下基础数据类型能够通过默认值轻松推导出来因此我们不需要专门设置只有在相对复杂的场景下才会需要专门声明。
// 能根据 0 推导为 number 类型const [count, setCount] useState(0);// 能根据 false 推导为 boolean 类型const [visible, setVisible] useState(false);// 能根据 [] 推导为 any[] 类型因此此时还需要专门声明any为何物const [arr, setArr] useState([]);
需要注意的是state 使用浅比较对比新旧数据。也就意味着当 state 为引用数据类型时如果你的新数据与旧数据引用相同那么 React 无法感知到你的数据发生了变化。
例如
import useState from react;export default function Counter() const [arr, setArr] useState([]) function incrementHandle() let newArr arr newArr.push((arr[arr.length - 1] || 0) 1) setArr(newArr) return ( arr.map((item) > ( item )) 递增 )
重点关注该例子中的 incrementHandle 方法。新的数组与旧的数组引用一样因此就算更改了数组内容但是 React 无法感知组件也就不会重新渲染。
因此正确的方式应该要想办法让新旧数据的引用不同
function incrementHandle() let newArr [...arr] newArr.push((arr[arr.length - 1] || 0) 1) setArr(newArr)
当 state 的数据变得复杂我们可以借助 immer 等不可变数据集来帮助我们。详情可阅读相关文档
注意state 是被监控的数据它与 UI 的变化息息相关。在实践中还有很多其他的数据与 UI 变化无关他们不应该放在 state 中来管理而应该想其他办法。
单向数据流
一个完整的 React 项目由多个组件组合而成。每个组件都是独立的都可以有自己的外部数据与内部数据。对于父组件来说它可以把自己的 state 作为 props 向下传递给它的子组件。
这种自上而下的数据流动我们称之为单向数据流.
任何一个组件的内部数据 state 发生变化就会影响组件树中低于它的组件。
如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源但是它只能向下流动。
在实践中为了避免额外的性能消耗我们需要精准的把握每一次 state 的更新会影响哪些组件掌握单向数据流的特性对此非常有帮助。
如果你想要在子组件中修改父组件传递而来的状态只能通过修改父组件 state 的方式修改方法通常也由父组件传递给子组件。
合并
当同一个 state 数据被修改多次时他们会合并成一次修改。如下面例子我们调用两次 setCount执行一次之后count 变成 2而不会变成 3
import useState from react;export default function Counter() const [count, setCount] useState(0) function handle() setCount(count 1) setCount(count 2) return ( count 递增 )
当我们同时修改多个 state 时也会合并起来被认为是一次修改组件只会重新渲染一次。
import useState from react;export default function Counter() const [count, setCount] useState(0) const [other, setOther] useState(0) function handle() setCount(count 1) setOther(other 1) return ( count other 递增 )
如果同时修改多个 state 的行为发生在异步回调里React 18 也会把它们合并成为一次 state 操作。
import useState from react;export default function Counter() const [count, setCount] useState(0) const [other, setOther] useState(0) function handle() setTimeout(() > setCount(count 1) setOther(other 1) , 500) return ( count other 递增 )
正确识别闭包
在函数组件中如果我们在回调函数中使用了 state 的值那么闭包就会产生。闭包在函数创建时产生他会缓存创建时的 state 的值。
在很多文章中把这种现象称为“闭包陷阱”它是一种正常现象但是如果你在使用时无法正确识别它那么会给你带来麻烦。
import useState from react;export default function Counter() const [count, setCount] useState(0) function handle() setCount(count 1) // 当 setTimeout 执行时 // 回调函数的 count 值不是 1而是 0 setTimeout(() > setCount(count 2) , 0) return ( count 递增 )
异步写法
如果我们要在 setTimeout 回调函数中正确的拿到当前 state 的值我们可以使用如下的写法来达到目的
import useState from react;export default function Counter() const [count, setCount] useState(0) function handle() setCount(count 1) setTimeout(() > - setCount(count 2) setCount(count > count 2) , 0) return ( count 递增 )
实践
接下来我们完成一个稍微复杂一点的例子
多个滑动条控制div元素的不同属性如果使用 useState 来实现应该怎么做
import React, useState from react;import Slider from antd-mobile;import ./index.scss;interface Color r: number, g: number, b: numberexport default function Rectangle() const [height, setHeight] useState(10); const [width, setWidth] useState(10); const [color, setColor] useState( r: 0, g: 0, b: 0 ); const [radius, setRadius] useState(0); const style height: $heightpx, width: $widthpx, backgroundColor: rgb($color.r, $color.g, $color.b), borderRadius: $radiuspx return (
height:
width:
color: R:
color: G:
color: B:
Radius:
聊点高级的
原则上来说state 的应用知识基本上已经聊完了。不过作为 React 专家我还能跟大家聊一点高级的。
state 的变化是异步的。
如果看过我《Javascript 核心进阶》对事件循环机制了解比较深刻的那么 state 异步潜藏的危机就容易被意识并解决它。如果不了解你可能会遇到大坑。
状态异步也就意味着当你想要在setCount之后立即去使用它时你无法拿到状态最新的值而到下一个事件循环周期执行时状态才是最新值。
const [count, setCount] useState(10);setCount(20);console.log(count); // 此时counter的值并不是20而是10
实践中有许多错误的使用会因为这个异步问题出现 bug。
例如我们想要用一个接口去请求一堆数据而这个接口接收多个参数。
当改变各种过滤条件那么就势必会改变传入的参数并在参数改变时立即重新去请求一次数据。
我们会很自然的想到使用如下的方式
import React, useState from react;interface ListItem name: string, id: number, thumb: string// 一堆各种参数interface Param current?: number, pageSize?: number, name?: string, id?: number, time?: Dateexport default function AsyncDemo() const [listData, setListData] useState([]); // 定义一个状态缓存参数确保每次改变后都能缓存完整的参数 const [param, setParam] useState(); function fetchListData() // ts-ignore listApi(param).then(res > setListData(res.data); ) function searchByName(name: string) setParam( ...param, name ); // 改变param之后立即执行请求数据的代码 // 这里的问题是因为异步的原因param并不会马上发生变化 // 此时直接发送请求无法拿到最新的参数 fetchListData(); return [ data list, searchByName(Jone)>search by name ]
这是一个不完整的例子代码只能用作参考需要大家在阅读时结合自身开发经验去体会。
关键的代码在于searchByName方法。当使用setParam改变了param之后立即去请求数据在当前循环周期param并没有改变。请求的结果自然无法达到预期。
那么如何解决这个问题呢提示我们要首先考虑一个数据是否一定要把他定义为 state想明白这个问题继续学习后面的章节相信你能找到答案
求点赞求分享。由于文章推送算法调整互动少不会推送到读者互动一下表示认可。
【文章转自:日本站群服务器 http://www.558idc.com/japzq.html处的文章,转载请说明出处】