這都拿不下你?React + Redux 讓你一眼就愛上的嗶哩嗶哩會員購
theme: cyanosis highlight: vs2015
我正在參加「創意開發 投稿大賽」詳情請看:掘金創意開發大賽來了!
繼上一篇文章React專案開發-仿嗶哩嗶哩移動端首頁之後,我經過一段時間的學習(主要是對Redux的學習)之後,寫下了這篇關於 React + Redux 專案的文章,新增了會員購介面,對首頁進行了改進,優化了使用者體驗,使用 Redux 對狀態統一進行管理。
前言
1. Redux 概述
Redux 是一個使用叫做 action
的事件來管理和更新應用狀態的模式和工具庫,它以集中式Store(centralized store)的方式對整個應用中使用的狀態進行集中管理,其規則確保狀態只能以可預測的方式更新。
2. 為什麼要使用 Redux?
Redux 提供的模式和工具讓我們更容易理解應用程式中的狀態何時、何地、為什麼以及如何更新,以及當這些更改發生時應用程式邏輯將如何表現。
3. 我們應該如何使用 Redux?
Redux 的應用場景:
- 在應用的大量地方,都存在大量的狀態
- 應用狀態會隨著時間的推移而頻繁更新
- 更新該狀態的邏輯可能很複雜
- 中型和大型程式碼量的應用,很多人協同開發
Redux 工作流
為了更好的理解,我們把 Redux
工作流比作圖書館借書流程,當我們(Component
)向管理員(Store
)發出一個借書行為(Action
)時,管理員接收到後,對照借書記錄本(Reducers
)檢視,管理員拿到新書(一個新的狀態
)後交給我們。
專案預覽:Github Pages
專案準備
安裝依賴
在使用 redux 之前,我們需要在之前的基礎上安裝以下依賴(預設安裝最新版本):
npm i redux
npm i react-redux
npm i redux-thunk
npm i redux-logger
redux-thunk
主要的功能就是讓我們可以 dispatch
一個函式,applyMiddleware
是 Redux 的一個原生方法,可將所有中介軟體組成一個數組,依次執行。
```js import { createStore, compose, applyMiddleware } from 'redux' import reducer from './reducer' import thunk from 'redux-thunk' // 非同步資料管理 import logger from 'redux-logger' // 讓 redux 除錯更優秀
const composeEnhancers = window.REDUX_DEVTOOLS_EXTENSION_COMPOSE || compose // 啟用 redux devtools
const store = createStore(reducer, // 合併成一箇中間件物件 compose( composeEnhancers(applyMiddleware(thunk)), applyMiddleware(logger) ) )
export default store ```
安裝外掛
為了能夠看到效果,我們還需在瀏覽器中(建議使用Chrome)安裝外掛:Redux DevTools,Redux DevTools外掛下載地址。
當我們激活了 redux devtools
後,切換到瀏覽器的 Redux
介面可以看到當前倉庫狀態:
redux-logger
會在 dispatch
改變倉庫狀態的時候打印出舊的倉庫狀態、當前觸發的action以及新的倉庫狀態。
實現功能
使用者體驗方面
1. 瀑布流佈局 + 圖片懶載入
multi-column
佈局中子元素的排列順序是先從上往下再從左至右的,讓上下相鄰的子元素分開使用 margin-bottom
即可。
- 瀑布流的優點如下:
- 節省空間,外表美觀,更有藝術性。
- 對於觸屏裝置非常友好,通過向上滑動瀏覽。
- 使用者瀏覽時的觀賞和思維不容易被打斷,留存更容易。
實現程式碼:
css
/* 父容器 */
.container {
column-count: 2; // 兩列布局
column-gap: 10px; // 列間距為 10px
}
/* 子元素 */
.good-box {
width: 100%;
break-inside: avoid; // 元素不能中斷,auto 可以中斷
}
實現效果:
當從遠端請求過來的圖片還沒加載出來時,使用預設圖片進行佔位,優化使用者體驗。這裡我勾選瀏覽器中的禁用快取模擬了一下效果:
2. 頁面切換
頁面切換的效果我使用了 CSSTransition
對子元素進行包裹,它會將過渡型別給到子元素,新增動畫效果。
實現程式碼:
```jsx import { CSSTransition } from 'react-transition-group' ...
<CSSTransition in={show} // 控制動畫的開關 timeout={300} // 動畫執行時間 appear={true} // 第一次載入該元件時啟用相應的動畫渲染 classNames="fly" unmountOnExit // 動畫效果消失時,該標籤會從 dom 樹上移除
... ```
```js import styled from "styled-components"
export const Wrapper = styled.div...
/* CSSTransition 過度型別給children */
&.fly-enter,&.fly-appear {
opacity: 0;
/* 啟用GPU加速 */
transform: translate3d(100%, 0, 0);
}
&.fly-enter-active, &.fly-apply-active {
opacity: 1;
transition: all .3s;
transform: translate3d(0, 0, 0);
}
&.fly-exit {
opacity: 1;
transform: translate3d(0,0,0)
}
&.fly-exit-active {
opacity: 0;
transition: all .3s;
transform: translate3d(100%, 0, 0);
}
```
效果如下:
3. 載入動畫
當我們首次進入到首頁或會員購頁面時,圖片資源是不能瞬間得到的,這裡我使用了 antd-mobile
的動態骨架屏,讓頁面更豐富,填補了等待時間段,提升使用者體驗。
在搜尋介面,我仿造了神三元寫的 loading
元件,這是一個在等待請求過程中的動畫效果。loading
動畫主要實現程式碼如下:
```jsx import React from 'react'; import styled, { keyframes } from 'styled-components';
const loading = keyframes0%, 100% {
transform: scale(0.0);
}
50% {
transform: scale(1.0);
}
const LoadingWrapper = styled.div>div {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
margin: auto;
width: 60px;
height: 60px;
opacity: 0.6;
border-radius: 50%;
background-color: rgba(0, 150, 250, 0.8); // 這裡可以選擇自己喜歡的顏色
animation: ${loading} 1.4s infinite ease-in; // 動畫持續時間
}
>div:nth-child(2) {
animation-delay: -0.7s; // 跳過 0.7s 進入動畫週期
}
function Loading() {
return (
export default React.memo(Loading); ```
效果如下:
業務方面
1. 商品收藏
這裡可能會遇到的問題,點選收藏某件商品時,把列表中的所有商品都收藏了,解決方法:
把商品元件進行單獨封裝作為子元件,父元件將 good
傳遞給子元件,子元件拿到單獨的 id
,在進行之後的操作時,就不會對其它的子元件造成影響。
```jsx import React from 'react' import propTypes from "prop-types"; import { Wrapper } from './style' import GoodsItem from '@/components/GoodsItem';
export default function GoodsList({goodsList}) {
return (
GoodsList.propTypes = { goodsList: propTypes.array.isRequired } ```
子元件 GoodsItem
中收藏效果的實現程式碼如下:
```jsx import React, { useState } from "react" import classnames from 'classnames'
const GoodsItem = ({good}) => { const [isColl, setIsColl] = useState(false) // 定義收藏狀態
const changeColl = () => { // 對狀態進行取反 setIsColl(!isColl) }
return (
// 效能優化 export default React.memo(GoodsItem) ```
2. 防抖搜尋功能
首頁搜尋功能由父元件 HomeSearch
和子元件 SearchBox
實現。
首頁搜尋
會員購搜尋功能的功能由父元件 VipSearch
和子元件 SearchBox
實現。
會員購搜尋
在搜尋上我加上了防抖功能,防抖函式和其他函式放到 util
資料夾下的 index.js
下作為工具使用。
js
// 防抖函式
export const debounce = (func, delay) => {
let timer
return function (...args) {
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(this, args)
clearTimeout(timer)
}, delay)
}
}
在子元件 SearchBox
中修改 query
,進行防抖處理,每隔500
毫秒執行一次 handleQuery
去更新父元件 VipSearch
中的 query
,並通過 dispatch
對狀態進行修改。
jsx
// useMomo 可以快取 上一次函式計算的結果
let handleQueryDebounce = useMemo(() => {
return debounce(handleQuery, 500) // 每隔 0.5s 執行一次
}, [handleQuery])
// 使用 useEffect 去更新
useEffect(() => {
handleQueryDebounce(query)
}, [query])
```jsx
// 父元件
const VipSearch = (props) => {
...
// 輸入時每隔 0.5s 執行一次
useEffect(() => {
if (query.trim()) {
changeEnterLoadingDispatch(true)
getGoodsListDispatch(query)
}
}, [query])
// 對商品標題進行模糊查詢,將搜尋到的商品進行渲染
const renderGoodsList = () => {
return (
商品列表
return (
const mapStateToProps = (state) => { return { enterLoading: state.vipsearch.enterLoading, goodsList: state.vipsearch.goodsList } }
const mapDispatchToProps = (dispatch) => { return { changeEnterLoadingDispatch(data) { dispatch(changeEnterLoading(data)) }, getGoodsListDispatch(query) { dispatch(getGoodsList(query)) } } }
export default connect(mapStateToProps, mapDispatchToProps)(React.memo(VipSearch)) ```
優化
1. 封裝網路請求
在 api
資料夾下,新添加了 config.js
檔案,用來對物件 axiosInstance
進行封裝,當介面數量較多時,能夠減少程式碼量,使頁面更簡潔:
```js import axios from 'axios' export const baseUrl = "https://www.fastmock.site/mock/059647e88be0d33ef58d6ab4bf009dd9/bilibili" // 單例設計模式 const axiosInstance = axios.create({ baseURL: baseUrl })
// 新增響應攔截,拿到資料時對資料做處理,或丟擲錯誤 axiosInstance.interceptors.response.use( res => res.data, err => { console.log(err, '網路錯誤~') } )
export { axiosInstance } ```
2. 骨架屏佔位
因為大多數圖片資源是從 fastmock
中請求過來的,受網路影響需一些時間,使用者在等待的過程中頁面出現空白狀態很影響體驗,引入骨架屏讓頁面更豐富,填補了等待時間段,優化了使用者體驗。此專案中我使用了 antd-mobile
中的動態骨架屏,Skeleton 骨架屏。
3. 圖片懶載入
圖片懶載入也叫“按需載入”,也就是當圖片資源出現在視口區域內,才會被載入,使用懶載入能大大節省網站的流量,對於有大量圖片資源的網站來說顯得尤為重要。
這裡我使用了 LazyLoad
,當網路圖片還沒加載出來時,使用本地預設圖片進行佔位,主要程式碼如下:
```jsx import LazyLoad from 'react-lazyload' import bilibili from '@/assets/images/bilibili.jpeg'
...
<LazyLoad
placeholder={}
... ```
4. memo效能優化
如果你的元件在相同 props
的情況下渲染相同的結果,那麼你可以通過將其包裝在 React.memo
中呼叫,以此通過記憶元件渲染結果的方式來提高元件的效能表現。這意味著在這種情況下,React 將跳過渲染元件的操作並直接複用最近一次渲染的結果。但React.memo
僅檢查 props
的變更。
5. 全域性樣式風格檔案
在專案開發過程中,我們在不同的頁面中可能會用到相同的樣式,例如背景顏色,字型大小,邊框等等,將相同的樣式抽離出來放到 assets
資料夾下的 global-style.js
中,便於對樣式進行統一管理。
js
export default {
"background-color": "rgba(50, 50, 50, 0.06)",
"search_bar-color": "rgba(50, 50, 50, 0.08)",
"border-color": "rgba(50, 50, 50, 0.2)",
"loading-color": "rgba(0, 150, 250, 0.8)"
}
定義了全域性樣式風格檔案後,我們就可以在其他的樣式檔案中進行引用,如下:
```js import styled from "styled-components" import style from '@/assets/global-style'
export const Wrapper = styled.divbackground: ${style["background-color"]};
...
```
最後
在這個專案中,我借鑑了神三元大佬的網易雲音樂專案中的 CSSTransition
元件, loading
元件和 debounce
防抖函式等,專案地址如下:github原始碼地址,如果有興趣的小夥伴也可以去瞧瞧他寫的掘金小冊React Hooks 與 Immutable 資料流實戰。
本專案在後期仍會繼續改進,實現更多功能,謝謝大家!未完待續......
原始碼地址:bilibili-page
專案預覽:GitHub Pages