国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【實(shí)戰(zhàn)】 九、深入React 狀態(tài)管理與Redux機(jī)制(一) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(十六)

這篇具有很好參考價(jià)值的文章主要介紹了【實(shí)戰(zhàn)】 九、深入React 狀態(tài)管理與Redux機(jī)制(一) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(十六)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。


學(xué)習(xí)內(nèi)容來(lái)源:React + React Hook + TS 最佳實(shí)踐-慕課網(wǎng)


相對(duì)原教程,我在學(xué)習(xí)開(kāi)始時(shí)(2023.03)采用的是當(dāng)前最新版本:

項(xiàng) 版本
react & react-dom ^18.2.0
react-router & react-router-dom ^6.11.2
antd ^4.24.8
@commitlint/cli & @commitlint/config-conventional ^17.4.4
eslint-config-prettier ^8.6.0
husky ^8.0.3
lint-staged ^13.1.2
prettier 2.8.4
json-server 0.17.2
craco-less ^2.0.0
@craco/craco ^7.1.0
qs ^6.11.0
dayjs ^1.11.7
react-helmet ^6.1.0
@types/react-helmet ^6.1.6
react-query ^6.1.0
@welldone-software/why-did-you-render ^7.0.1
@emotion/react & @emotion/styled ^11.10.6

具體配置、操作和內(nèi)容會(huì)有差異,“坑”也會(huì)有所不同。。。


一、項(xiàng)目起航:項(xiàng)目初始化與配置

  • 一、項(xiàng)目起航:項(xiàng)目初始化與配置

二、React 與 Hook 應(yīng)用:實(shí)現(xiàn)項(xiàng)目列表

  • 二、React 與 Hook 應(yīng)用:實(shí)現(xiàn)項(xiàng)目列表

三、TS 應(yīng)用:JS神助攻 - 強(qiáng)類(lèi)型

  • 三、 TS 應(yīng)用:JS神助攻 - 強(qiáng)類(lèi)型

四、JWT、用戶認(rèn)證與異步請(qǐng)求

  • 四、 JWT、用戶認(rèn)證與異步請(qǐng)求(上)

  • 四、 JWT、用戶認(rèn)證與異步請(qǐng)求(下)

五、CSS 其實(shí)很簡(jiǎn)單 - 用 CSS-in-JS 添加樣式

  • 五、CSS 其實(shí)很簡(jiǎn)單 - 用 CSS-in-JS 添加樣式(上)

  • 五、CSS 其實(shí)很簡(jiǎn)單 - 用 CSS-in-JS 添加樣式(下)

六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理

  • 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(上)

  • 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(中)

  • 六、用戶體驗(yàn)優(yōu)化 - 加載中和錯(cuò)誤狀態(tài)處理(下)

七、Hook,路由,與 URL 狀態(tài)管理

  • 七、Hook,路由,與 URL 狀態(tài)管理(上)

  • 七、Hook,路由,與 URL 狀態(tài)管理(中)

  • 七、Hook,路由,與 URL 狀態(tài)管理(下)

八、用戶選擇器與項(xiàng)目編輯功能

  • 八、用戶選擇器與項(xiàng)目編輯功能(上)

  • 八、用戶選擇器與項(xiàng)目編輯功能(下)

九、深入React 狀態(tài)管理與Redux機(jī)制

1.useCallback應(yīng)用,優(yōu)化異步請(qǐng)求

當(dāng)前項(xiàng)目中使用 useAsync 進(jìn)行異步請(qǐng)求,但是其中有一個(gè)隱藏 bug,若是在頁(yè)面中發(fā)起一個(gè)請(qǐng)求,這個(gè)請(qǐng)求需要較長(zhǎng)時(shí)間3s(可以使用開(kāi)發(fā)控制臺(tái)設(shè)置請(qǐng)求最短時(shí)間來(lái)預(yù)設(shè)場(chǎng)景),在這個(gè)時(shí)間段內(nèi),退出登錄,此時(shí)就會(huì)有報(bào)錯(cuò):

Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

原因是雖然退出登錄,組件銷(xiāo)毀,但是異步函數(shù)還在執(zhí)行,當(dāng)它執(zhí)行完進(jìn)行下一步操作 setXXX 或是 更新組件都找不到對(duì)應(yīng)已銷(xiāo)毀的組件。

接下來(lái)解決一下這個(gè)問(wèn)題。

編輯 src\utils\index.ts

...
/**
 * 返回組件的掛載狀態(tài),如果還沒(méi)有掛載或者已經(jīng)卸載,返回 false; 反之,返回 true;
 */
export const useMountedRef = () => {
  const mountedRef = useRef(false)

  useEffect(() => {
    mountedRef.current = true
    return () => {
      mountedRef.current = false
    }
  }, [])

  return mountedRef
}

src\utils\use-async.ts 上應(yīng)用:

...
import { useMountedRef } from "utils";
...
export const useAsync = <D>(...) => {
  ...
  const mountedRef = useMountedRef()
  ...
  const run = (...) => {
    ...
    return promise
      .then((data) => {
        if(mountedRef.current)
          setData(data);
        return data;
      })
      .catch((error) => {...});
  };
  ...
};

還有個(gè)遺留問(wèn)題,在 useEffect 中使用的變量若是沒(méi)有在依賴(lài)數(shù)組中添加就會(huì)報(bào)錯(cuò),添加上又會(huì)造成死循環(huán),因此之前用 eslint-disable-next-line 解決

// eslint-disable-next-line react-hooks/exhaustive-deps

現(xiàn)在換個(gè)方案,使用 useMemo 當(dāng)然可以解決,這里推薦使用特殊版本的 useMemo, useCallback

修改 src\utils\use-async.ts

import { useCallback, useState } from "react";
...

export const useAsync = <D>(...) => {
  ...

  const setData = useCallback((data: D) =>
    setState({
      data,
      stat: "success",
      error: null,
    }), [])

  const setError = useCallback((error: Error) =>
    setState({
      error,
      stat: "error",
      data: null,
    }), [])

  // run 來(lái)觸發(fā)異步請(qǐng)求
  const run = useCallback((...) => {
      ...
    }, [config.throwOnError, mountedRef, setData, state, setError],
  )
  ...
};

可以按照提示配置依賴(lài):React Hook useCallback has missing dependencies: 'config.throwOnError', 'mountedRef', 'setData', and 'state'. Either include them or remove the dependency array. You can also do a functional update 'setState(s => ...)' if you only need 'state' in the 'setState' call.e

盡管如此,但還是難免會(huì)出現(xiàn),在 useCallback 中改變 依賴(lài)值的行為,比如依賴(lài)值 XXX 對(duì)應(yīng)的 setXXX,這時(shí)需要用到 setXXX 的函數(shù)用法(這樣也可以省去一個(gè)依賴(lài)):

繼續(xù)修改 src\utils\use-async.ts

...
export const useAsync = <D>(...) => {
  ...
  const run = useCallback((...) => {
      ...
      setState(prevState => ({ ...prevState, stat: "loading" }));
      ...
    }, [config.throwOnError, mountedRef, setData, setError],
  )
  ...
};

修改 src\utils\project.ts

...
import { useCallback, useEffect } from "react";
...

export const useProjects = (...) => {
  ...
  const fetchProject = useCallback(() =>
    client("projects", { data: cleanObject(param || {})
  }), [client, param])

  useEffect(() => {
    run(fetchProject(), { rerun: fetchProject });
  }, [param, fetchProject, run]);
  ...
};
...

修改 src\utils\http.ts

...
import { useCallback } from "react";
...
export const useHttp = () => {
  ...
  return useCallback((...[funcPath, customConfig]: Parameters<typeof http>) =>
    http(funcPath, { ...customConfig, token: user?.token }), [user?.token]);
};

總結(jié):非狀態(tài)類(lèi)型需要作為依賴(lài) 就要將其使用 useMemo 或者 useCallback 包裹(依賴(lài)細(xì)化 + 新舊關(guān)聯(lián)),常見(jiàn)于 Custom Hook 中函數(shù)類(lèi)型數(shù)據(jù)的返回

2.狀態(tài)提升,組合組件與控制反轉(zhuǎn)

接下來(lái)定制化一個(gè)項(xiàng)目編輯模態(tài)框(編輯+新建項(xiàng)目),PageHeader hover 后可以打開(kāi)(新建),ProjectList 中可以打開(kāi)模態(tài)框(新建),里面的 List 的每行也可以打開(kāi)模態(tài)框(編輯)

src\components\lib.tsx 中新增 padding0Button

...
export const ButtonNoPadding = styled(Button)`
  padding: 0;
`

新建 src\screens\ProjectList\components\ProjectModal.tsx(模態(tài)框):

import { Button, Drawer } from "antd"

export const ProjectModal = ({isOpen, onClose}: { isOpen: boolean, onClose: () => void }) => {
  return <Drawer onClose={onClose} open={isOpen} width="100%">
    <h1>Project Modal</h1>
    <Button onClick={onClose}>關(guān)閉</Button>
  </Drawer>
}

新建 src\screens\ProjectList\components\ProjectPopover.tsx

import styled from "@emotion/styled"
import { Divider, List, Popover, Typography } from "antd"
import { ButtonNoPadding } from "components/lib"
import { useProjects } from "utils/project"


export const ProjectPopover = ({ setIsOpen }: { setIsOpen: (isOpen: boolean) => void }) => {
  const { data: projects } = useProjects()
  const starProjects = projects?.filter(i => i.star)

  const content = <ContentContainer>
    <Typography.Text type="secondary">收藏項(xiàng)目</Typography.Text>
    <List>  
      {
        starProjects?.map(project => <List.Item>
          <List.Item.Meta title={project.name}/>
        </List.Item>)
      }
    </List>
    <Divider/>
    <ButtonNoPadding type='link' onClick={() => setIsOpen(true)}>創(chuàng)建項(xiàng)目</ButtonNoPadding>
  </ContentContainer>
  return <Popover placement="bottom" content={content}>
    項(xiàng)目
  </Popover>
}

const ContentContainer = styled.div`
  width: 30rem;
`

編輯 src\authenticated-app.tsx(引入 ButtonNoPadding、 ProjectPopoverProjectModal 自定義組件,并將模態(tài)框的狀態(tài)管理方法傳到對(duì)應(yīng)組件 PageHeaderProjectList,注意接收方要定義好類(lèi)型):

...
import { ButtonNoPadding, Row } from "components/lib";
...
import { ProjectModal } from "screens/ProjectList/components/ProjectModal";
import { useState } from "react";
import { ProjectPopover } from "screens/ProjectList/components/ProjectPopover";

export const AuthenticatedApp = () => {
  const [isOpen, setIsOpen] = useState(false)
  ...

  return (
    <Container>
      <PageHeader setIsOpen={setIsOpen}/>
      <Main>
        <Router>
          <Routes>
            <Route path="/projects" element={<ProjectList setIsOpen={setIsOpen}/>} />
            ...
          </Routes>
        </Router>
      </Main>
      <ProjectModal isOpen={isOpen} onClose={() => setIsOpen(false)}/>
    </Container>
  );
};
const PageHeader = ({ setIsOpen }: { setIsOpen: (isOpen: boolean) => void }) => {
  ...

  return (
    <Header between={true}>
      <HeaderLeft gap={true}>
        <ButtonNoPadding type="link" onClick={resetRoute}>
          <SoftwareLogo width="18rem" color="rgb(38,132,255)" />
        </ButtonNoPadding>
        <ProjectPopover setIsOpen={setIsOpen}/>
        <span>用戶</span>
      </HeaderLeft>
      <HeaderRight>
        ...
      </HeaderRight>
    </Header>
  );
};
...

由于涉及登錄后多個(gè)組件會(huì)發(fā)起調(diào)用,因此 ProjectModal 組件需要放在 AuthenticatedAppContainer

編輯 src\screens\ProjectList\index.tsx(引入 模態(tài)框的狀態(tài)管理方法):

...
import { Row, Typography } from "antd";
...
import { ButtonNoPadding } from "components/lib";

export const ProjectList = ({ setIsOpen }: { setIsOpen: (isOpen: boolean) => void }) => {
  ...
  return (
    <Container>
      <Row justify='space-between'>
        <h1>項(xiàng)目列表</h1>
        <ButtonNoPadding type='link' onClick={() => setIsOpen(true)}>創(chuàng)建項(xiàng)目</ButtonNoPadding>
      </Row>
      ...
      <List
        setIsOpen={setIsOpen}
        {...}
      />
    </Container>
  );
};
...

編輯 src\screens\ProjectList\components\List.tsx(引入 模態(tài)框的狀態(tài)管理方法):

import { Dropdown, MenuProps, Table, TableProps } from "antd";
...
import { ButtonNoPadding } from "components/lib";
...
interface ListProps extends TableProps<Project> {
  ...
  setIsOpen: (isOpen: boolean) => void;
}

export const List = ({ users, setIsOpen, ...props }: ListProps) => {
  ...
  return (
    <Table
      pagination={false}
      columns={[
        ...
        {
          render: (text, project) => {
            const items: MenuProps["items"] = [
              {
                key: 'edit',
                label: "編輯",
                onClick: () => setIsOpen(true)
              },
            ];
            return <Dropdown menu={{ items }}>
              <ButtonNoPadding type="link" onClick={(e) => e.preventDefault()}>...</ButtonNoPadding>
            </Dropdown>
          }
        }
      ]}
      {...props}
    ></Table>
  );
};

可以明顯看到,這種方式的狀態(tài)提升(prop drilling)若是間隔層數(shù)較多時(shí)(定義和使用相隔太遠(yuǎn)),不僅有“下鉆”問(wèn)題,而且耦合度太高

下面使用 組件組合(component composition)的方式解耦

組件組合(component composition) | Context – React

編輯 src\authenticated-app.tsx(將 綁定了模態(tài)框 打開(kāi)方法的 ButtonNoPadding 作為屬性傳給需要用到的組件):

...
export const AuthenticatedApp = () => {
  ...
  return (
    <Container>
      <PageHeader projectButton={
        <ButtonNoPadding type="link" onClick={() => setIsOpen(true)}>
          創(chuàng)建項(xiàng)目
      </ButtonNoPadding>
      } />
      <Main>
        <Router>
          <Routes>
            <Route
              path="/projects"
              element={<ProjectList projectButton={
                <ButtonNoPadding type="link" onClick={() => setIsOpen(true)}>
                  創(chuàng)建項(xiàng)目
              </ButtonNoPadding>
              } />}
            />
            ...
          </Routes>
        </Router>
      </Main>
      ...
    </Container>
  );
};
const PageHeader = (props: { projectButton: JSX.Element }) => {
  ...
  return (
    <Header between={true}>
      <HeaderLeft gap={true}>
        ...
        <ProjectPopover { ...props } />
        ...
      </HeaderLeft>
      <HeaderRight>...</HeaderRight>
    </Header>
  );
};
...

編輯 src\screens\ProjectList\components\ProjectPopover.tsx(使用傳入的屬性組件代替之前的 綁定了模態(tài)框 打開(kāi)方法的 ButtonNoPadding ):

...
export const ProjectPopover = ({ projectButton }: { projectButton: JSX.Element }) => {
  ...
  const content = (
    <ContentContainer>
      ...
      { projectButton }
    </ContentContainer>
  );
  ...
};
...

編輯 src\screens\ProjectList\index.tsx(使用傳入的屬性組件代替之前的 綁定了模態(tài)框 打開(kāi)方法的 ButtonNoPadding 并繼續(xù)“下鉆”):

...
export const ProjectList = ({ projectButton }: { projectButton: JSX.Element }) => {
  ...
  return (
    <Container>
      <Row justify="space-between">
        ...
        { projectButton }
      </Row>
      ...
      <List
        projectButton={projectButton}
        {...}
      />
    </Container>
  );
};
...

編輯 src\screens\ProjectList\components\List.tsx(使用傳入的屬性組件代替之前的 綁定了模態(tài)框 打開(kāi)方法的 ButtonNoPadding ):

...
interface ListProps extends TableProps<Project> {
  ...
  projectButton: JSX.Element
}

// type PropsType = Omit<ListProps, 'users'>
export const List = ({ users, ...props }: ListProps) => {
  ...
  return (
    <Table
      pagination={false}
      columns={[
        ...
        {
          render: (text, project) => {
            return (
              <Dropdown 
                dropdownRender={() => props.projectButton}>
                <ButtonNoPadding
                  type="link"
                  onClick={(e) => e.preventDefault()}
                >
                  ...
                </ButtonNoPadding>
              </Dropdown>
            );
          },
        },
      ]}
      {...props}
    ></Table>
  );
};
  • 編輯按鈕這里使用并不恰當(dāng),不過(guò)這不是最終解決方案,理解思路即可
  • 淺析控制反轉(zhuǎn) - 知乎

部分引用筆記還在草稿階段,敬請(qǐng)期待。。。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-616401.html

到了這里,關(guān)于【實(shí)戰(zhàn)】 九、深入React 狀態(tài)管理與Redux機(jī)制(一) —— React17+React Hook+TS4 最佳實(shí)踐,仿 Jira 企業(yè)級(jí)項(xiàng)目(十六)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包