Taro 小程式開發大型實戰(五):使用 Hooks 版的 Redux 實現應用狀態管理(下篇)

tuture發表於2020-05-07

我們研發開源了一款基於 Git 進行技術實戰教程寫作的工具,我們圖雀社群的所有教程都是用這款工具寫作而成,歡迎 Star

如果你想快速瞭解如何使用,歡迎閱讀我們的 教程文件哦

歡迎繼續閱讀《Taro 小程式開發大型實戰》系列,前情回顧:

這是使用 Hooks 版的 Redux 重構狀態管理的下篇,在上篇中我們實現了 user 部分 的狀態管理的重構,但受限於篇幅,我們還剩下 Footer 元件部分沒有重構,在這一篇中,我們將首先實現 Footer 元件的狀態管理的重構,接著我們馬上來實現 post 邏輯的狀態管理的重構。

如果你不熟悉 Redux,推薦閱讀我們的《Redux 包教包會》系列教程:

本文所涉及的原始碼都放在了 Github 上,如果您覺得我們寫得還不錯,希望您能給❤️這篇文章點贊+Github倉庫加星❤️哦~

搞定 Footer 的 Redux 化

本來這個小標題我是不想起的,但是因為,是吧,大家上面在沒有小標題的情況下看了這麼久,可能已經廢(累)了,所以我就貼心的加上一個小標題,幫助你定位接下來講解的重心。

是的接下來,我們要重構 “我的” tab 頁面中的下半部分元件 src/components/Footer/index.js 我們遵循自頂向下的方式來重構,首先是 src/components/Logout/index.js 檔案,我們開啟這個檔案,對其中內容作出如下修改:

import Taro, { useState } from '@tarojs/taro'
import { AtButton } from 'taro-ui'
import { useDispatch } from '@tarojs/redux'

import { SET_LOGIN_INFO } from '../../constants'

export default function LoginButton(props) {
  const [isLogout, setIsLogout] = useState(false)
  const dispatch = useDispatch()

  async function handleLogout() {
    setIsLogout(true)

    try {
      await Taro.removeStorage({ key: 'userInfo' })

      dispatch({
        type: SET_LOGIN_INFO,
        payload: {
          avatar: '',
          nickName: '',
        },
      })
    } catch (err) {
      console.log('removeStorage ERR: ', err)
    }

    setIsLogout(false)
  }

  return (
    <AtButton type="secondary" full loading={isLogout} onClick={handleLogout}>
      退出登入
    </AtButton>
  )
}

這一步可能是最能體現引入 Redux 進行狀態管理帶來好處的一步了 – 我們將之前至上而下的 React 狀態管理邏輯壓平,使得底層元件可以在自身中就解決響應的狀態和邏輯問題。

可以看到,我們上面的檔案中主要有五處改動:

  • 首先我們從 @tarojs/taro 裡面匯出 useState Hooks。
  • 接著我們將之前在 src/pages/mine/mine.js 中定義的 isLogout 狀態移動到元件 Logout 元件內部來,因為它只和此元件有關係。
  • 接著我們用 isLogout 替換在 AtButton 裡面用到的 props.loading 屬性。
  • 然後,我們考慮將之前按鈕點選呼叫 props.handleLogout Redux 化,我們將這個點選之後的回撥函式 handleLogout 在元件內部定義。
  • 最後,我們從 @tarojs/redux 中匯入 useDispatch Hooks,並在元件中呼叫成我們需要的 dispatch 函式,接著我們在 handleLogout 函式中去 dispatch 一個 SET_LOGIN_INFO action 來重置 Store 中的 nickNameavatar 屬性。

提示

這裡我們在元件內定義的 handleLogout 函式和我們之前在 src/pages/mine/mine.js 中定義的類似,只是使用 dispatch action 的方式替換了重置 nickNameavatar 的部分。

搞定完 Logout 元件,接著就是 LoginForm 元件的重構了,讓我們快馬加鞭,讓它也接受 Redux 光環的洗禮吧!

開啟 src/components/LoginForm/index.jsx ,對其中的內容作出相應的修改如下:

import Taro, { useState } from '@tarojs/taro'
import { View, Form } from '@tarojs/components'
import { AtButton, AtImagePicker } from 'taro-ui'
import { useDispatch } from '@tarojs/redux'

import { SET_LOGIN_INFO, SET_IS_OPENED } from '../../constants'
import './index.scss'

export default function LoginForm(props) {
  // Login Form 登入資料
  const [formNickName, setFormNickName] = useState('')
  const [files, setFiles] = useState([])
  const [showAddBtn, setShowAddBtn] = useState(true)

  const dispatch = useDispatch()

  function onChange(files) {
    if (files.length > 0) {
      setShowAddBtn(false)
    } else {
      setShowAddBtn(true)
    }

    setFiles(files)
  }

  function onImageClick() {
    Taro.previewImage({
      urls: [props.files[0].url],
    })
  }

  async function handleSubmit(e) {
    e.preventDefault()

    // 鑑權資料
    if (!formNickName || !files.length) {
      Taro.atMessage({
        type: 'error',
        message: '您還有內容沒有填寫!',
      })

      return
    }

    setShowAddBtn(true)

    // 提示登入成功
    Taro.atMessage({
      type: 'success',
      message: '恭喜您,登入成功!',
    })

    // 快取在 storage 裡面
    const userInfo = { avatar: files[0].url, nickName: formNickName }

    // 清空表單狀態
    setFiles([])
    setFormNickName('')

    // 快取在 storage 裡面
    await Taro.setStorage({ key: 'userInfo', data: userInfo })

    dispatch({ type: SET_LOGIN_INFO, payload: userInfo })

    // 關閉彈出層
    dispatch({ type: SET_IS_OPENED, payload: { isOpened: false } })
  }

  return (
    <View className="post-form">
      <Form onSubmit={handleSubmit}>
        <View className="login-box">
          <View className="avatar-selector">
            <AtImagePicker
              length={1}
              mode="scaleToFill"
              count={1}
              files={files}
              showAddBtn={showAddBtn}
              onImageClick={onImageClick}
              onChange={onChange}
            />
          </View>
          <Input
            className="input-nickName"
            type="text"
            placeholder="點選輸入暱稱"
            value={formNickName}
            onInput={e => setFormNickName(e.target.value)}
          />
          <AtButton formType="submit" type="primary">
            登入
          </AtButton>
        </View>
      </Form>
    </View>
  )
}

這一步和上一步類似,可能也是最能體現引入 Redux 進行狀態管理帶來好處的一步了,我們同樣將之前在頂層元件中提供的狀態壓平到了底層元件內部。

可以看到,我們上面的檔案中主要有四處改動:

  • 首先我們將 formNickNamefiles 等狀態放置到 LoginForm 元件內部,並使用 useState Hooks 管理起來,因為它們只和此元件有關係。
  • 接著,我們將 AtImagePicker 裡面的 props.files 替換成 files,將它的 onChange 回撥函式內部的設定改變狀態的 props.handleFilesSelect(files) 替換成 setFiles(files)。可以看到這裡我們還對 files.length = 0 的形式做了一個判斷,當沒有選擇圖片時,要把我們選擇圖片的按鈕顯示出來。
  • 接著,我們將 Input 元件的 props.formNickName 替換成 formNickName,將之前 onInput 接收的回撥函式換成了 setFormNickName 的形式來設定 formNickName 的變化。
  • 接著,我們將之前提交表單需要呼叫的父元件方法 props.handleSubmit 移動到元件內部來定義,可以看到,這個 hanldeSubmit 組合了之前在 src/components/Footer/index.jsxsrc/pages/mine/mine.js 元件裡的 handleSubmit 邏輯:
    • 首先使用 e.preventDefault 禁止瀏覽器預設行為。
    • 接著進行資料驗證,不合要求的資料就會被駁回並顯示錯誤(其實這裡應該顯示警告 warning,當時寫程式碼時石樂志?)。
    • 接著因為 LoginForm 表單資料要被清除,所以我們將選中圖片的按鈕又設定為可顯示狀態。
    • 接著提示登入成功。
    • 清空表單狀態。
    • 將登入資料快取在 storage 裡面,在 Taro 裡面使用 Taro.setStorage({ key, data }) 的形式來快取,其中 key 是字串,data 是字串或者物件。
      • 最後我們匯出了 useDispatch Hooks,使用 useDispatch Hooks 生成的 dispatch 函式的引用來發起更新 Redux store 的 action 來更新本地資料,typeSET_LOGIN_INFO 的 action 用來更新使用者登入資訊,typeSET_IS_OPENED 的 action 用來更新 isOpened 屬性,它將關閉展示登入框的彈出層 FloatLayout 元件。

講到這裡,我們的 Footer 部分的重構大業還剩下臨門一腳了。讓我們開啟 src/components/Footer/index.js 檔案,立馬來重構它:

import Taro from '@tarojs/taro'
import { View } from '@tarojs/components'
import { AtFloatLayout } from 'taro-ui'
import { useSelector, useDispatch } from '@tarojs/redux'

import Logout from '../Logout'
import LoginForm from '../LoginForm'
import './index.scss'
import { SET_IS_OPENED } from '../../constants'

export default function Footer(props) {
  const nickName = useSelector(state => state.user.nickName)

  const dispatch = useDispatch()

  // 雙取反來構造字串對應的布林值,用於標誌此時是否使用者已經登入
  const isLogged = !!nickName

  // 使用 useSelector Hooks 獲取 Redux Store 資料
  const isOpened = useSelector(state => state.user.isOpened)

  return (
    <View className="mine-footer">
      {isLogged && <Logout />}
      <View className="tuture-motto">
        {isLogged ? 'From 圖雀社群 with Love ❤' : '您還未登入'}
      </View>
      <AtFloatLayout
        isOpened={isOpened}
        title="登入"
        onClose={() =>
          dispatch({ type: SET_IS_OPENED, payload: { isOpened: false } })
        }
      >
        <LoginForm />
      </AtFloatLayout>
    </View>
  )
}

可以看到上面的程式碼主要有五處改動:

  • 首先我們已經將 nickName 抽取到 Redux store 儲存的狀態中,所以之前從父元件獲取的 props.isLogged 判斷是否登入的資訊,我們移動到元件內部來,使用 useSelector Hooks 從 Redux store 從獲取 nickName 屬性,進行雙取反操作成布林值來表示是否已經登入的 isLogged 屬性,並使用它來替換之前的 props.isLogged 屬性。
  • 接著,就是取代之前從父元件獲取的 props.isOpened 屬性,我們使用 useSelector Hooks 從 Redux store 中獲取對應的 isOpened 屬性,然後替換之前的 props.isOpened,使用者控制登入框視窗的彈出層 AtFloatLayout 的開啟和關閉。
  • 接著,我們將之前 AtFloatLayout 關閉時(onClose)的回撥函式替換成 dispatch 一個 typeSET_IS_OPENED 的 action 來設定 isOpened 屬性將 AtFloatLayout 關閉。
  • 接著,我們開始移除 LogoutLoginForm 元件上不再需要傳遞的屬性,因為在對應的元件中我們已經宣告瞭對應的屬性了。
  • 最後,我們刪掉之前定義在 Footer 元件內的 formNickNamefiles 等狀態,以及不再需要的 handleSubmit 函式,因為它已經在 LoginForm 裡面定義了。

完成 “我的” 頁面重構

熟悉套路的同學可能都知道起這個標題的含義了吧 ?。

我們一路打怪重構到這裡,相比眼尖的人已經摸清楚 Redux 的套路了,結合 Redux 來寫 React 程式碼,就好比 “千里之堤,始於壘土” 一般,我們先把所有細小的分支元件搞定,進而一步一步向頂層元件進發,以完成所有元件的編寫。

而這個 src/pages/mine/mine.jsx 元件就是 “我的” 這一 tab 頁面的頂層元件了,也是我們在 “我的” 頁面需要重構的最後一個頁面了,是的,我們馬上就要達到第一階段性勝利了✌️。現在就開啟這個檔案,對其中的內容作出如下的修改:

import Taro, { useEffect } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { useDispatch } from '@tarojs/redux'

import { Header, Footer } from '../../components'
import './mine.scss'
import { SET_LOGIN_INFO } from '../../constants'

export default function Mine() {
  const dispatch = useDispatch()

  useEffect(() => {
    async function getStorage() {
      try {
        const { data } = await Taro.getStorage({ key: 'userInfo' })

        const { nickName, avatar } = data

        // 更新 Redux Store 資料
        dispatch({ type: SET_LOGIN_INFO, payload: { nickName, avatar } })
      } catch (err) {
        console.log('getStorage ERR: ', err)
      }
    }

    getStorage()
  })

  return (
    <View className="mine">
      <Header />
      <Footer />
    </View>
  )
}

Mine.config = {
  navigationBarTitleText: '我的',
}

可以看到,上面的程式碼做了一下五處改動:

  • 我們匯入了 useDispatch Hooks 和 SET_LOGIN_INFO 常量,並把之前在 getStorage 方法裡面設定 nickNameavatar 的操作替換成了 dispatch 一個 typeSET_LOGIN_INFO 的 action。
  • 接著我們刪除不再需要的 formNickNamefilesisLogoutisOpened 狀態,以及 setLoginInfohandleLogouthandleSetIsOpenedhandleClickhandleSubmit 方法。
  • 最後我們刪除 HeaderFooter 元件上不再不需要的屬性。

大功告成?!這裡給你頒發一個銀牌,以獎勵你能一直堅持閱讀並跟到這裡,我們這一篇教程很長很長,能跟下來的都不容易,希望你能在心裡或用實際行動給自己鼓鼓掌?。

小憩一下,恢復精力,整裝待發!很多同學可能很好奇了,為什麼還只能拿一個銀牌呢?那是因為我們的重構程式才走了一半呀✌️,但是不要擔心,我們所有新的東西都已經講完了,接下來就只是一些收尾工作了,當你能堅持到終點的時候,會有驚喜等著你哦!加油吧騷年?。

開始重構 “首頁” 之旅

我們依然按照之前的套路,從最底層的元件開始重構,首先是我們的登入框彈出層 LoginForm 元件,讓我們開啟 src/components/PostForm/index.jsx 檔案,對其中的內容作出相應的修改如下:

import Taro, { useState } from '@tarojs/taro'
import { View, Form, Input, Textarea } from '@tarojs/components'
import { AtButton } from 'taro-ui'
import { useDispatch, useSelector } from '@tarojs/redux'

import './index.scss'
import { SET_POSTS, SET_POST_FORM_IS_OPENED } from '../../constants'

export default function PostForm(props) {
  const [formTitle, setFormTitle] = useState('')
  const [formContent, setFormContent] = useState('')

  const nickName = useSelector(state => state.user.nickName)
  const avatar = useSelector(state => state.user.avatar)

  const dispatch = useDispatch()

  async function handleSubmit(e) {
    e.preventDefault()

    if (!formTitle || !formContent) {
      Taro.atMessage({
        message: '您還有內容沒有填寫完哦',
        type: 'warning',
      })

      return
    }

    dispatch({
      type: SET_POSTS,
      payload: {
        post: {
          title: formTitle,
          content: formContent,
          user: { nickName, avatar },
        },
      },
    })

    setFormTitle('')
    setFormContent('')

    dispatch({
      type: SET_POST_FORM_IS_OPENED,
      payload: { isOpened: false },
    })

    Taro.atMessage({
      message: '發表文章成功',
      type: 'success',
    })
  }

  return (
    <View className="post-form">
      <Form onSubmit={handleSubmit}>
        <View>
          <View className="form-hint">標題</View>
          <Input
            className="input-title"
            type="text"
            placeholder="點選輸入標題"
            value={formTitle}
            onInput={e => setFormTitle(e.target.value)}
          />
          <View className="form-hint">正文</View>
          <Textarea
            placeholder="點選輸入正文"
            className="input-content"
            value={formContent}
            onInput={e => setFormContent(e.target.value)}
          />
          <AtButton formType="submit" type="primary">
            提交
          </AtButton>
        </View>
      </Form>
    </View>
  )
}

這個檔案的形式和我們之前的 src/components/LoginForm/index.jsx 檔案類似,可以看到,我們上面的檔案中主要有四處改動:

  • 首先我們將 formTitleformContent 等狀態放置到 PostForm 元件內部,並使用 useState Hooks 管理起來,因為它們只和此元件有關係。
  • 接著,我們將 Input 裡面的 props.formTitle 替換成 formTitle,將它的 onInput 回撥函式內部的設定改變狀態的 props. handleTitleInput 替換成 setFormTitle(e.target.value) 的回撥函式。
  • 接著,我們將 Textarea 元件的 props. formContent 替換成 formContent,將之前 onInput 接收的回撥函式換成了 setFormContent 的形式來設定 formContent 的變化。
  • 最後,我們將之前提交表單需要呼叫的父元件方法 props.handleSubmit 移動到元件內部來定義,可以看到,這個 hanldeSubmit 和我們之前定義在 src/pages/index/index.js 元件裡的 handleSubmit 邏輯類似:
    • 首先使用 e.preventDefault 禁止瀏覽器預設行為。
    • 接著進行資料驗證,不合要求的資料就會被駁回並顯示警告(這裡我們又顯示對了?)。
    • 接著 dispatch 一個 typeSET_POSTS 的 action,將新發表的 post 新增到 Redux store 對應的 posts 陣列中。我們注意到這裡我們使用 useSelector Hooks 從 Redux store 裡面獲取了 nickNameavatar 屬性,並把它們組合到 post.user 屬性裡,隨著 action 的 payload 一起被 dispatch,我們用這個 user 屬性標誌發帖的使用者屬性。
    • 清空表單狀態。
    • 接著我們 dispatch 一個 typeSET_POST_FORM_IS_OPENED 的 action 用來更新 isOpened 屬性,它將關閉展示發表帖子的表單彈出層 FloatLayout 元件。
    • 最後提示發帖成功。

接著是我們 “首頁” 頁面元件另外一個底層子元件 PostCard,它主要用於展示一個帖子,讓我們 src/components/PostCard/index.jsx 檔案,對其中的內容作出對應的修改如下:

import Taro from '@tarojs/taro'
import { View } from '@tarojs/components'
import classNames from 'classnames'
import { AtAvatar } from 'taro-ui'

import './index.scss'

export default function PostCard(props) {
  // 注意:
  const { title = '', content = '', user } = props.post
  const { avatar, nickName } = user || {}

  const handleClick = () => {
    // 如果是列表,那麼就響應點選事件,跳轉到帖子詳情
    if (props.isList) {
      Taro.navigateTo({
        url: `/pages/post/post?postId=${props.postId}`,
      })
    }
  }

  const slicedContent =
    props.isList && content.length > 66
      ? `${content.slice(0, 66)} ...`
      : content

  return (
    <View
      className={classNames('at-article', { postcard__isList: props.isList })}
      onClick={handleClick}
    >
      <View className="post-header">
        <View className="at-article__h1">{title}</View>
        <View className="profile-box">
          <AtAvatar circle size="small" image={avatar} />
          <View className="at-article__info post-nickName">{nickName}</View>
        </View>
      </View>
      <View className="at-article__content">
        <View className="at-article__section">
          <View className="at-article__p">{slicedContent}</View>
        </View>
      </View>
    </View>
  )
}

PostCard.defaultProps = {
  isList: '',
  post: [],
}

可以看到這個元件基本不保有自己的狀態,它接收來自父元件的狀態,我們對它的修改主要有下面五個部分:

  • 將之前的直接獲取 props.titleprops.content 放到了 props.post 屬性中,我們從 props.post 屬性中匯出我們需要展示的 titlecontent,還要一個額外的 user 屬性,它應該是一個物件,儲存著發帖人的使用者屬性,我們使用解構的方法獲取 user.avataruser.nickName 的值。
  • 接著我們看到 return 的元件結構發生了很大的變化,這裡我們為了方便,使用了 taro-ui 提供給我們的 Article 文章樣式元件,用於展示類似微信公眾號文章頁的一些樣式,可供使用者快速呈現文章內容,可以詳情可以檢視 taro-ui 連結,有了 taro-ui 加持,我們就額外的展示了發表此文章的使用者頭像(avatar)和暱稱(nickName)。
  • 我們還可以看到,這裡我們對原 content 做了一點修改,當 PostCard 元件在文章列表中被引用的時候,我們對內容長度進行截斷,當超過 66 字元時,我們就截斷它,並加上省略號 ...
  • 最後,我們改動了 handleClick 方法,之前是在跳轉路由的頁面路徑裡直接帶上查詢引數 titlecontent ,當我們要傳遞的內容多了,這個路徑就會顯得很臃腫,所以這裡我們傳遞此文章對應的 id,這樣可以通過此 id 取到完整的 post 資料,使路徑保持簡潔,這也是最佳實踐的推薦做法。

接著我們補充一下在 PostCard 元件裡面會用到的樣式,開啟 src/components/PostCard/index.scss 檔案,補充和改進對應的樣式如下:

@import '~taro-ui/dist/style/components/article.scss';

.postcard {
  margin: 30px;
  padding: 20px;
}

.postcard__isList {
  border-bottom: 1px solid #ddd;
  padding-bottom: 20px;
}

.post-header {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.profile-box {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.post-nickName {
  color: #777;
}

可以看到我們更新了一些樣式,然後引入了 taro-ui 提供給我們的 article 文章樣式。

重構完 “首頁” 頁面元件的所有底層元件,我們開始完成最終的頂層元件,開啟 src/pages/index/index.jsx 檔案,對相應的內容修改如下:

import Taro, { useEffect } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { AtFab, AtFloatLayout, AtMessage } from 'taro-ui'
import { useSelector, useDispatch } from '@tarojs/redux'

import { PostCard, PostForm } from '../../components'
import './index.scss'
import { SET_POST_FORM_IS_OPENED, SET_LOGIN_INFO } from '../../constants'

export default function Index() {
  const posts = useSelector(state => state.post.posts) || []
  const isOpened = useSelector(state => state.post.isOpened)
  const nickName = useSelector(state => state.user.nickName)

  const isLogged = !!nickName

  const dispatch = useDispatch()

  useEffect(() => {
    async function getStorage() {
      try {
        const { data } = await Taro.getStorage({ key: 'userInfo' })

        const { nickName, avatar } = data

        // 更新 Redux Store 資料
        dispatch({ type: SET_LOGIN_INFO, payload: { nickName, avatar } })
      } catch (err) {
        console.log('getStorage ERR: ', err)
      }
    }

    getStorage()
  })

  function setIsOpened(isOpened) {
    dispatch({ type: SET_POST_FORM_IS_OPENED, payload: { isOpened } })
  }

  function handleClickEdit() {
    if (!isLogged) {
      Taro.atMessage({
        type: 'warning',
        message: '您還未登入哦!',
      })
    } else {
      setIsOpened(true)
    }
  }

  console.log('posts', posts)

  return (
    <View className="index">
      <AtMessage />
      {posts.map((post, index) => (
        <PostCard key={index} postId={index} post={post} isList />
      ))}
      <AtFloatLayout
        isOpened={isOpened}
        title="發表新文章"
        onClose={() => setIsOpened(false)}
      >
        <PostForm />
      </AtFloatLayout>
      <View className="post-button">
        <AtFab onClick={handleClickEdit}>
          <Text className="at-fab__icon at-icon at-icon-edit"></Text>
        </AtFab>
      </View>
    </View>
  )
}

Index.config = {
  navigationBarTitleText: '首頁',
}

可以看到我們上面的內容有以下五處改動:

  • 首先我們匯出了 useSelector 鉤子,然後從 Redux store 中獲取了 postsisOpenednickName 等屬性。
  • 接著,我們將之前定義在 PostCard 元件上的屬性進行了一次換血,之前是直接傳遞 titlecontent 屬性,現在我們傳遞整個 post 屬性,並且額外傳遞了一個 postId 屬性,用於在 PostCard 裡面點選跳轉路由時進行標註。
  • 接著,我們去掉 PostForm 元件上面的所有屬性,因為我們已經在元件內部定義了它們。
  • 接著,我們使用 useEffect Hooks,在裡面定義並呼叫了 getStorage 方法,獲取了我們儲存在 storage 裡面的使用者登入資訊,如果使用者登入了,我們 dispatch 一個 typeSET_LOGIN_INFO 的 action,將這份登入資訊儲存在 Redux store 裡面以供後續使用。
  • 最後,我們將 AtFabonClick 回撥函式替換成 handleClickEdit,在其中對使用者點選進行判斷,如果使用者未登入,那麼彈出警告,告知使用者,如果使用者已經登入,那麼就 dispatch 一個 typeSET_POST_FORM_IS_OPENED 的 action 去設定 isOpened 屬性,開啟發帖的彈出層,允許使用者進行發帖操作。

以重構 “文章詳情” 頁結束

最後,讓我們堅持一下,跑贏重構工作的最後一公里?!完成 “文章詳情” 頁的重構。

讓我們開啟 src/pages/post/post.jsx 檔案,對其中的內容作出相應的修改如下:

import Taro, { useRouter } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { useSelector } from '@tarojs/redux'

import { PostCard } from '../../components'
import './post.scss'

export default function Post() {
  const router = useRouter()
  const { postId } = router.params

  const posts = useSelector(state => state.post.posts)
  const post = posts[postId]

  console.log('posts', posts, postId)

  return (
    <View className="post">
      <PostCard post={post} />
    </View>
  )
}

Post.config = {
  navigationBarTitleText: '帖子詳情',
}

可以看到,上面的檔案做了以下四處修改:

  • 我們從 router.params 中匯出了 postId,因為之前我們在 PostCard 裡面點選跳轉的路徑引數使用了 postId
  • 接著我們匯入並使用 useSelector Hooks 獲取了儲存在 Redux store 中的 posts 屬性,然後使用上一步獲取到的 postId,來獲取我們最終要渲染的 post 屬性。
  • 最後,我們將傳給 PostCard 的屬性改成上一步獲取到的 post

注意

這裡的 console.log 是除錯時使用的,生產環境中建議刪掉。

檢視效果

可以看到,在未登入狀態下,會提示請登入:

在已登入的情況下,發帖子會顯示當前登入使用者的頭像和暱稱:

小結

有幸!到這裡,我們 Redux 重構之旅的萬里長征就跑完了!讓我們來回顧一下我們在這一小節中學到了那些東西。

  • 首先我們講解了使用 Redux 的初衷,接著我們安裝了相關依賴,然後引出了 Redux 三大核心概念:Store、Action、Reducers,接著我們建立了應用需要的兩個 Reducer:postuser;接著我們將將 Redux 和 React 整合起來;因為 Action 是從元件中 dispatch 出來了,所以我們接下來就開始了元件的重構之旅。

  • 在重構 “我的” 頁面元件時,我們按照 Redux 的思想,從它的底層元件三個登入按鈕重構開始,接著重構了 LoggedMine 元件,再往上就是 Header 元件;重構完 Header 元件之後,我們接著從 Footer 元件的底層元件 Logout 元件開始重構,然後重構了 LoginForm 元件,最後是 Footer 元件,重構完 HeaderFooter 元件,我們開始重構其上層元件 mine 頁面元件,自此我們就完成了 “我的” 頁面的重構。

  • 在重構 “首頁” 頁面元件時,我們同樣按照 Redux 的思想,從它的底層元件 PostForm 元件開始,接著是 PostCard 元件,最後再回到頂層元件 index 首頁頁面元件。

在重構 “帖子詳情” 頁面元件時,因為其底層元件 PostCard 已經重構過了,所以我們就直接重構了 post 帖子詳情頁面元件。

能跟著這麼長的文章堅持到這裡,我想給你鼓個掌,也希望你能給自己鼓個掌,我想,我可以非常肯定且自豪的頒佈給你第一名的獎章了?。

終於,這漫長的第五篇結束了。在接下來的文章中,我們將接觸小程式雲後臺開發,並在前端接入後臺資料。

想要學習更多精彩的實戰技術教程?來圖雀社群逛逛吧。

本文所涉及的原始碼都放在了 Github 上,如果您覺得我們寫得還不錯,希望您能給❤️這篇文章點贊+Github倉庫加星❤️哦

本作品採用《CC 協議》,轉載必須註明作者和本文連結

圖雀社群

相關文章