当前位置:首页 > 科技  > 软件

useEffect 实践案例之一

来源: 责编: 时间:2023-11-30 09:27:54 143观看
导读序对于 useEffect 的掌握是 React hooks 学习的重中之重。因此我们还需要花一些篇幅继续围绕它讲解。在上一篇文章中,我们使用两个案例分析了 useEffect 的理论知识。接下来,我们通过一些具体的实践案例来学习 useEffec

6hh28资讯网——每日最新资讯28at.com

对于 useEffect 的掌握是 React hooks 学习的重中之重。因此我们还需要花一些篇幅继续围绕它讲解。6hh28资讯网——每日最新资讯28at.com

在上一篇文章中,我们使用两个案例分析了 useEffect 的理论知识。接下来,我们通过一些具体的实践案例来学习 useEffect 的运用。6hh28资讯网——每日最新资讯28at.com

一、需求

现有一个简单的需求,要实现一个搜索框,输入内容之后,点击搜索按钮,然后得到一个列表。6hh28资讯网——每日最新资讯28at.com

当列表为空时,显示暂无数据。6hh28资讯网——每日最新资讯28at.com

6hh28资讯网——每日最新资讯28at.com

接口请求过程中,需要显示 Loading 状态。6hh28资讯网——每日最新资讯28at.com

6hh28资讯网——每日最新资讯28at.com

Loading 状态随便用的一个转圈图标来表示,和下面的图标有点重叠,以后有机会再调整一下 UI。6hh28资讯网——每日最新资讯28at.com

接口请求成功之后,显示一个列表。6hh28资讯网——每日最新资讯28at.com

6hh28资讯网——每日最新资讯28at.com

再次搜索时,显示 Loading 状态。6hh28资讯网——每日最新资讯28at.com

6hh28资讯网——每日最新资讯28at.com

如果接口请求出错,显示错误页面。6hh28资讯网——每日最新资讯28at.com

6hh28资讯网——每日最新资讯28at.com

在实践中,这是针对一个请求所需要的常规状态处理,当然很多时候我们在学习的过程中简化了空数据/Loading/异常等状态,就导致了许多自学的朋友没有在工作中友好处理这些状态的习惯。6hh28资讯网——每日最新资讯28at.com

二、实现

我们一步一步来实现该需求。6hh28资讯网——每日最新资讯28at.com

我们假设一个请求需要花费 600ms,在学习阶段,我们可以借助 Promise 与 setTimeout 来模拟一个接口请求。6hh28资讯网——每日最新资讯28at.com

单独创建一个 api.ts 文件。6hh28资讯网——每日最新资讯28at.com

在该文件中,我们声明一个名为 searchApi 的函数,该函数接收一个字符串作为参数。6hh28资讯网——每日最新资讯28at.com

我计划设计该函数最终返回一个 Promise 对象。并将一个字符串数组 resolve 出来。该字符串由搜索条件的一个字符与Math.random 产生的随机数组成。6hh28资讯网——每日最新资讯28at.com

输出的列表长这样。6hh28资讯网——每日最新资讯28at.com

6hh28资讯网——每日最新资讯28at.com

该 api 函数具体代码如下:6hh28资讯网——每日最新资讯28at.com

// ./api.tsexport function searchApi(param: string) {  return new Promise<string[]>((resolve, reject) => {    const p = param.split('')    const arr: string[] = []    for(var i = 0; i < 10; i++) {      const pindex = i % p.length      arr.push(`${p[pindex] || '^ ^'} - ${Math.random()}`)    }    setTimeout(() => {      if (Math.random() * 10 > 1) {        resolve(arr)      } else {        reject('请求异常,请重新尝试!')      }    }, 600)  })}

在该函数中,我们使用泛型明确了 Promise 的输出类型,在后续的使用中就可以利用 TypeScript 的自动类型推导得到具体的返回类型。6hh28资讯网——每日最新资讯28at.com

6hh28资讯网——每日最新资讯28at.com

接下来我们要创建组件函数。6hh28资讯网——每日最新资讯28at.com

// index.tsxexport default function DemoOneNormal() {  // ...}

然后我们根据 UI 的情况去分析应该在代码中设计哪些数据。6hh28资讯网——每日最新资讯28at.com

首先有一个列表需要展示。6hh28资讯网——每日最新资讯28at.com

const [list, setList] = useState<string[]>([])

然后有一个 Loading 的显示与隐藏需要控制。6hh28资讯网——每日最新资讯28at.com

const [loading, setLoading] = useState(false)

还有一个错误信息需要显示。6hh28资讯网——每日最新资讯28at.com

const [error, setError] = useState('')

还有一个稍微有一些特殊的,输入框中输入的内容。我们要注意准确分析内容:该内容的展示在已有的 UI 中,是根据键盘输入而展示内容,它不由数据来驱动。6hh28资讯网——每日最新资讯28at.com

我们在该案例中,仅仅只是记录输入的内容,并传入 searchApi即可。因此我们可以使用 useRef 来存储该变量。6hh28资讯网——每日最新资讯28at.com

const str = useRef('')

如果情况有变,有其他的 UI 需要该数据来驱动,那么我们就需要将其调整为使用 useState 来存储。6hh28资讯网——每日最新资讯28at.com

接下来思考 JSX 代码的编写。6hh28资讯网——每日最新资讯28at.com

首先是一个输入框 input 与按钮 button。6hh28资讯网——每日最新资讯28at.com

<input   className={s.input}   placeholder="请输入您要搜索的内容"   notallow={(e) => str.current = e.target.value} /><Button   className={s.button}   onClick={onSure}>  搜索</Button>

案例中的样式使用了 css module,因此 className 的语法会与前面介绍的有所不同,我们把 s.input 当成一个字符串来看待即可。6hh28资讯网——每日最新资讯28at.com

代码中,借助 input 的 onChange 回调来记录当前输入的值。6hh28资讯网——每日最新资讯28at.com

// const str = useRef('')notallow={(e) => str.current = e.target.value}

点击按钮时,修改对应的状态,并开始发送请求。此时 Loading 应该修改为 true。6hh28资讯网——每日最新资讯28at.com

function onSure() {  setLoading(true)  searchApi(str.current).then(res => {    setList(res)    setLoading(false)    setError('')  }).catch(err => {    setLoading(false)    setError(err)  })}

请求成功之后,Loading 改回 false,list 得到新的数据。如果请求失败,Loading 依然需要改成 false,并记录错误信息。6hh28资讯网——每日最新资讯28at.com

接下来我们要思考列表的 UI 代码。6hh28资讯网——每日最新资讯28at.com

首先,空数据、错误信息、正常列表的显示情况是互斥的,他们三个只能存在一个。Loading 状态是每个情况下都有可能发生的,与他们的关系是分别共存的。6hh28资讯网——每日最新资讯28at.com

因此,当有错误信息时,这一块的内容应该为。6hh28资讯网——每日最新资讯28at.com

if (error) {  return (    <div className={s.wrapper}>      {loading && (        <div className={s.loading_wrapper}>          <Icon spin type='loading' style={{ fontSize: 40 }} />        </div>      )}      <Icon type='event' color='red' style={{ fontSize: 32 }} />      <div className={s.error}>{error}</div>    </div>  )}

案例中出现的 Icon 组件是一个图标,该组件是我们这个项目自己封装好的基础组件。6hh28资讯网——每日最新资讯28at.com

当是空列表时。6hh28资讯网——每日最新资讯28at.com

if (list.length === 0) {  return (    <div className={s.wrapper}>      {loading && (        <div className={s.loading_wrapper}>          <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />        </div>      )}      <Icon type='event' color='#ccc' style={{ fontSize: 32 }} />      <div className={s.nodata}>暂无数据</div>    </div>  )}

正常列表有数据时。6hh28资讯网——每日最新资讯28at.com

<div className={s.list}>  {loading && (    <div className={s.loading_wrapper}>      <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />    </div>  )}  {list.map(item => (    <div key={item} className={s.item}>{item}</div>  ))}</div>

OK,此时所有的逻辑已经考虑完毕。6hh28资讯网——每日最新资讯28at.com

三、优化封装

我们会发现,列表相关的逻辑实在是有点繁琐。如果每次遇到一个列表就要处理这么多,岂不是非常消耗时间?6hh28资讯网——每日最新资讯28at.com

因此我们这里考虑将这些逻辑统一封装到 List 组件里,下次要使用直接拿出来用就可以了。6hh28资讯网——每日最新资讯28at.com

// ./List/index.tsxexport default function List(props) {}

在封装时,我们首先要考虑哪些属性需要作为 props 传入该 List 组件。关于封装的思考,和其他的逻辑封装是一样的,我们需要先考虑在不同的场景之下,他们的共性与差异分别是什么,差异的部分作为参数传入。6hh28资讯网——每日最新资讯28at.com

三个数据,error,loading,list 都是差异部分,他们需要作为 props 传入。6hh28资讯网——每日最新资讯28at.com

先定义一个类型声明如下:6hh28资讯网——每日最新资讯28at.com

interface ListProps<T> {  loading?: boolean,  error?: string,  list?: T[]}

此时我们看到由于 list 的每一项具体数据内容,可能每一个列表都不一样,我们无法在这里确认他的类型,因此此处使用泛型来表示。6hh28资讯网——每日最新资讯28at.com

不知道 list 的每一项具体数据是什么,也就意味着对应的 UI 我们也无法提前得知,只有在使用时才知道,因此还应该补上一个新的 props 属性。6hh28资讯网——每日最新资讯28at.com

interface ListProps<T> {  loading?: boolean,  error?: string,  list?: T[],+ renderItem: (item: T) => ReactNode}

然后我们只需要把差异部分与共同部分在组件逻辑中组合起来即可,List 组件完整代码如下:6hh28资讯网——每日最新资讯28at.com

import Icon from 'components/Icon'import { ReactNode } from 'react'import s from './index.module.scss'interface ListProps<T> {  loading?: boolean,  error?: string,  list?: T[],  renderItem: (item: T) => ReactNode}export default function List<T>(props: ListProps<T>) {  const {list = [], loading, error, renderItem} = props  if (error) {    return (      <div className={s.wrapper}>        {loading && (          <div className={s.loading_wrapper}>            <Icon spin type='loading' style={{ fontSize: 40 }} />          </div>        )}        <Icon type='event' color='red' style={{ fontSize: 32 }} />        <div className={s.error}>{error}</div>      </div>    )  }  if (list.length === 0) {    return (      <div className={s.wrapper}>        {loading && (          <div className={s.loading_wrapper}>            <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />          </div>        )}        <Icon type='event' color='#ccc' style={{ fontSize: 32 }} />        <div className={s.nodata}>暂无数据</div>      </div>    )  }  return (    <div className={s.list}>      {loading && (        <div className={s.loading_wrapper}>          <Icon spin type='loading' color='#2860Fa' style={{ fontSize: 38 }} />        </div>      )}      {list.map(renderItem)}    </div>  )}

封装好之后,使用起来就非常简单了,我们只需要把当前上下文中的数据传入进去即可。6hh28资讯网——每日最新资讯28at.com

<List   list={list}   loading={loading}    error={error}  renderItem={(item) => (    <div key={item} className={s.item}>{item}</div>  )}/>

该案例组件文件路径:src/pages/demos/effect/search/Normal.tsx6hh28资讯网——每日最新资讯28at.com

四、需求改进

在某些场景,初始化时我们并不需要展示空数组,而是需要请求一次接口,然后展示对应的列表,因此,在这种需求的情况下,代码需要进行一些调整。6hh28资讯网——每日最新资讯28at.com

首先,Loading 的初始化状态需要从 false 改为 true,表示一开始就会立即请求数据。6hh28资讯网——每日最新资讯28at.com

- const [loading, setLoading] = useState(false)+ const [loading, setLoading] = useState(true)

然后初始化请求数据的操作,在 useEffect 中完成,传入空数组作为依赖项,表示只在组件首次渲染完成之后执行一次。6hh28资讯网——每日最新资讯28at.com

... + useEffect(() => {+   searchApi(str.current).then(res => {+     setList(res)+     setLoading(false)+     setError('')+   }).catch(err => {+     setLoading(false)+     setError(err)+   })+ }, [])function onSure() {  setLoading(true)  searchApi(str.current).then(res => {    setList(res)    setLoading(false)    setError('')  }).catch(err => {    setLoading(false)    setError(err)  })}...

OK,这样需求就完整的被解决,不过此时我们发现,useEffect 的逻辑与 onSure 的逻辑高度重合,他们一个代表初始化逻辑,一个代表更新逻辑。6hh28资讯网——每日最新资讯28at.com

因此在代码上做一些简单的调整。6hh28资讯网——每日最新资讯28at.com

function getList() {    searchApi(str.current).then(res => {      setList(res)      setLoading(false)      setError('')    }).catch(err => {      setLoading(false)      setError(err)    })  }  useEffect(() => {    getList()  }, [])  function onSure() {    setLoading(true)    getList()  }

这样调整了之后,我们发现一个有趣的事情,当点击搜索按钮触发 onSure 时,我们会执行一次把 loading 修改为 true 的操作。6hh28资讯网——每日最新资讯28at.com

setLoading(true)

那如果这个时候,我们就可以把 loading 作为 useEffect 的依赖项传入,onSure 里就可以只保留这一行代码。6hh28资讯网——每日最新资讯28at.com

useEffect(() => {  loading && getList()}, [loading])function onSure() {  setLoading(true)}

这就是我们在本书唯一付费章节「React 哲学」中提到的开关思维。在日常生活中,如果我想要打开电视机,我们只需要关注开关按钮那一下操作,在这里也是一样,如果我想要重新请求列表搜索,我只需要关注如何操作 loading 这个开关即可6hh28资讯网——每日最新资讯28at.com

该案例组件文件路径:src/pages/demos/effect/search/Normal2.tsx。6hh28资讯网——每日最新资讯28at.com

接下来我们将要学习自定义 hook,进一步感受开关思维的魅力。6hh28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-35286-0.htmluseEffect 实践案例之一

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 一篇学会用 KEDA 根据工作负载进行快速扩容

下一篇: 消息队列备选架构选择,你选择哪个?

标签:
  • 热门焦点
Top