Taro 小程序开发大型实战(五):使用 Hooks 版的 Redux 实现应用状态管理(下篇)
欢迎继续阅读《Taro 小程序开发大型实战》系列,前情回顾:
- 熟悉的 React,熟悉的 Hooks:我们用 React 和 Hooks 实现了一个非常简单的添加帖子的原型
- 多页面跳转和 Taro UI 组件库:我们用 Taro 自带的路由功能实现了多页面跳转,并用 Taro UI 组件库升级了应用界面
- 实现微信和支付宝多端登录:实现了微信、支付宝以及普通登录和退出登录
- 使用 Hooks 版的 Redux 实现大型应用状态管理(上篇):使用 Hooks 版的 Redux 实现了
user
逻辑的状态管理重构
这是使用 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 中的nickName
和avatar
属性。
提示
这里我们在组件内定义的
handleLogout函数和我们之前在src/pages/mine/mine.js中定义的类似,只是使用 dispatch action 的方式替换了重置nickName和avatar的部分。
搞定完
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 进行状态管理带来好处的一步了,我们同样将之前在顶层组件中提供的状态压平到了底层组件内部。
可以看到,我们上面的文件中主要有四处改动:
- 首先我们将
formNickName
和files
等状态放置到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.jsx
和src/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 来更新本地数据,type
为SET_LOGIN_INFO
的 action 用来更新用户登录信息,type
为SET_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 一个type
为SET_IS_OPENED
的 action 来设置isOpened
属性将AtFloatLayout
关闭。 - 接着,我们开始移除
Logout
和LoginForm
组件上不再需要传递的属性,因为在对应的组件中我们已经声明了对应的属性了。 - 最后,我们删掉之前定义在
Footer
组件内的formNickName
和files
等状态,以及不再需要的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
方法里面设置nickName
和avatar
的操作替换成了 dispatch 一个type
为SET_LOGIN_INFO
的 action。 - 接着我们删除不再需要的
formNickName
、files
、isLogout
、isOpened
状态,以及setLoginInfo
、handleLogout
、handleSetIsOpened
、handleClick
、handleSubmit
方法。 - 最后我们删除
Header
和Footer
组件上不再不需要的属性。
大功告成🥈!这里给你颁发一个银牌,以奖励你能一直坚持阅读并跟到这里,我们这一篇教程很长很长,能跟下来的都不容易,希望你能在心里或用实际行动给自己鼓鼓掌👏。
小憩一下,恢复精力,整装待发!很多同学可能很好奇了,为什么还只能拿一个银牌呢?那是因为我们的重构进程才走了一半呀✌️,但是不要担心,我们所有新的东西都已经讲完了,接下来就只是一些收尾工作了,当你能坚持到终点的时候,会有惊喜等着你哦!加油吧骚年💪。
开始重构 “首页” 之旅
我们依然按照之前的套路,从最底层的组件开始重构,首先是我们的登录框弹出层
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文件类似,可以看到,我们上面的文件中主要有四处改动:
- 首先我们将
formTitle
和formContent
等状态放置到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 一个
type
为SET_POSTS
的 action,将新发表的 post 添加到 Redux store 对应的posts
数组中。我们注意到这里我们使用useSelector
Hooks 从 Redux store 里面获取了nickName
和avatar
属性,并把它们组合到post.user
属性里,随着 action 的 payload 一起被 dispatch,我们用这个user
属性标志发帖的用户属性。 - 清空表单状态。
- 接着我们 dispatch 一个
type
为SET_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.title
和props.content
放到了props.post
属性中,我们从props.post
属性中导出我们需要展示的title
和content
,还要一个额外的user
属性,它应该是一个对象,保存着发帖人的用户属性,我们使用解构的方法获取user.avatar
和user.nickName
的值。 - 接着我们看到
return
的组件结构发生了很大的变化,这里我们为了方便,使用了taro-ui
提供给我们的Article
文章样式组件,用于展示类似微信公众号文章页的一些样式,可供用户快速呈现文章内容,可以详情可以查看 taro-ui 链接,有了taro-ui
加持,我们就额外的展示了发表此文章的用户头像(avatar
)和昵称(nickName
)。 - 我们还可以看到,这里我们对原
content
做了一点修改,当PostCard
组件在文章列表中被引用的时候,我们对内容长度进行截断,当超过 66 字符时,我们就截断它,并加上省略号...
。 - 最后,我们改动了
handleClick
方法,之前是在跳转路由的页面路径里直接带上查询参数title
和content
,当我们要传递的内容多了,这个路径就会显得很臃肿,所以这里我们传递此文章对应的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 中获取了posts
、isOpened
和nickName
等属性。 - 接着,我们将之前定义在
PostCard
组件上的属性进行了一次换血,之前是直接传递title
和content
属性,现在我们传递整个post
属性,并且额外传递了一个postId
属性,用于在PostCard
里面点击跳转路由时进行标注。 - 接着,我们去掉
PostForm
组件上面的所有属性,因为我们已经在组件内部定义了它们。 - 接着,我们使用
useEffect
Hooks,在里面定义并调用了getStorage
方法,获取了我们保存在storage
里面的用户登录信息,如果用户登录了,我们 dispatch 一个type
为SET_LOGIN_INFO
的 action,将这份登录信息保存在 Redux store 里面以供后续使用。 - 最后,我们将
AtFab
的onClick
回调函数替换成handleClickEdit
,在其中对用户点击进行判断,如果用户未登录,那么弹出警告,告知用户,如果用户已经登录,那么就 dispatch 一个type
为SET_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:
post
和user
;接着我们将将 Redux 和 React 整合起来;因为 Action 是从组件中 dispatch 出来了,所以我们接下来就开始了组件的重构之旅。 -
在重构 “我的” 页面组件时,我们按照 Redux 的思想,从它的底层组件三个登录按钮重构开始,接着重构了
LoggedMine
组件,再往上就是Header
组件;重构完Header
组件之后,我们接着从Footer
组件的底层组件Logout
组件开始重构,然后重构了LoginForm
组件,最后是Footer
组件,重构完Header
和Footer
组件,我们开始重构其上层组件mine
页面组件,自此我们就完成了 “我的” 页面的重构。 -
在重构 “首页” 页面组件时,我们同样按照 Redux 的思想,从它的底层组件
PostForm
组件开始,接着是PostCard
组件,最后再回到顶层组件index
首页页面组件。
在重构 “帖子详情” 页面组件时,因为其底层组件
PostCard已经重构过了,所以我们就直接重构了
post帖子详情页面组件。
能跟着这么长的文章坚持到这里,我想给你鼓个掌,也希望你能给自己鼓个掌,我想,我可以非常肯定且自豪的颁布给你第一名的奖章了🥇。
终于,这漫长的第五篇结束了。在接下来的文章中,我们将接触小程序云后台开发,并在前端接入后台数据。
想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。
本文所涉及的源代码都放在了 Github 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点赞+Github仓库加星❤️哦
- 点赞
- 收藏
- 分享
- 文章举报
- Taro 小程序开发大型实战(七):尝鲜微信小程序云(下篇)
- 深入浅出React之第三章:使用redux管理应用状态
- Vue技术栈开发实战(四)-------------状态管理bus的使用
- Taro 小程序开发大型实战(八):尝鲜 LeanCloud Serverless 云服务
- C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 - 适合大型企业信息化应用使用的角色权限管理体系
- 微信小程序如何使用 Git 实现版本管理和协作开发
- react结合redux和react-router开发大型应用实现按需加载(code splitting)
- Taro 小程序开发大型实战(六):尝鲜微信小程序云(上篇)
- 使用 Redux 管理状态,第 3 部分: 使用 Redux 实现异步操作
- ios开发-UI基础-应用管理(单纯界面)改进5-使用代理实现监听下载按钮的点击(delegate)
- 老歌新唱--使用VB6开发的ActiveX实现.NET程序的混淆加密
- 使用PEAR/MDB2开发PHP和MYSQL程序[含Smarty应用]
- 使用C#实现DHT磁力搜索的BT种子后端管理程序+数据库设计(开源)[搜片神器]
- 使用.net框架应用C#语言开发窗口程序的一些注意事项
- 在Windows Mobile和Wince(Windows Embedded CE)下如何使用.NET Compact Framework开发进程管理程序
- 精益开发实战(用看板管理大型项目)读后感
- iOS开发UI篇—在UITableview的应用中使用动态单元格来完成app应用程序管理界面的搭建
- Windows Mobile应用开发(1):使用Win32 SDK 开发屏幕手电程序
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-通过SQL实现特殊业务
- 使用Visual Studio(VS)开发Qt程序代码提示功能的实现