1:1 纯手工打造:OPPO 商城2.0 - 超详细小白React+redux入门项目讲解

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

一、前言

继数周前通过React完成了 OPPO商城的部分界面后,随着近几周学习的深入,接触到了 redux ,在学习后知道了其对于数据管理方面的强大之处后,决定将之前写的 OPPO 商城进行升级,于是就有了这一版 React + redux 的 OPPO 商城2.0版本

接下来就通过以实战 OPPO 商城的方式一起走入 React + redux 的世界

二、redux

1. 何为redux

redux1.png

  • Store:Store 就是保存数据的地方,你可以把它看成一个容器。
  • State:Store对象包含所有数据。
  • Action:在原来的useState中,State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
  • ActionCreators:指明Action变化的种类
  • dispatch() :是 View 发出 Action 的唯一方法。
  • Reducer:Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

就假如这个组件是一场战争,store就是一个司令,它呢就负责统筹全局;Action Creators 就是通讯员将战场信息(state)通过发报员dispatch发送给参谋长Reducers来处理;之后将新的信息(newState)报告给司令Store在由司令来排兵布阵。

就本组件而言,其数据量并不大,所以并不能够体现出redux 由多么强大和方便。但是为了方便以后为大型项目开发做准备,也同样为了方便理解redux这里还是统一用了redux来管理数据。也一改之前项目在组件中用useState来引入数据;它虽然剥夺了页面管理数据的能力,但是通过一个Store来管理数据更加便于我们多人协作,以及数据的后期管理。

三、组件详情

1、组件展示

左边为React开发OPPO商城,右边为原始商城页面

屏幕截图 2022-06-30 232201.png classify.png

2、组件更新

本项目基 OPPO商城1.0(1:1 纯手工打造:OPPO 商城 - 超详细小白React入门项目讲解 - 掘金 (juejin.cn)) 的基础上做的更新有:

  1. 将本项目全部改用redux进行数据管理
  2. 进行了接口请求的封装
  3. 增加了calssify组件页面

四、组件实现

1. 组件解构与组件树

屏幕截图 2022-07-24 205955.png

组件与组件树与上一个版本并没有多大的不同,只是在需要运用redux管理数据的组件页面多了一个Store文件

2、封装网络请求

屏幕截图 2022-07-24 210359.png

屏幕截图 2022-07-24 210413.png

这个版本的api网络请求文件夹我分为了两个文件来封装,避免了request文件过于复杂和重复,也为后期管理和修改请求文件提供便利;害在config文件中设置了一个拦截器来确保正确性,一旦输入错误会抛出我们写的err。

3、redux

1、总仓的创建

1、先用 Provider 包裹,相当于给 App 根组件提供仓库服务

<Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
  

2、 创建总仓库Store

import { createStore, compose, applyMiddleware } from 'redux'
import thunk from 'redux-thunk' 
import reducer from './reducer'

const composeEnhancers =
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,
    composeEnhancers(
        applyMiddleware(thunk)
    )
)

export default store;
  • applyMiddleware :提供中间件 thunk除此之外还有 logger 等
  • composeEnhancers :将多个中间件合并在一起,方便调试。我用的是 Redux DevTools

3、 总Reducer 文件 用来集合所有子仓的reducer

import { combineReducers } from 'redux'
import { reducer as classifyReducer } from '../pages/Classify/store/index'
import { reducer as homeReducer } from '../pages/Home/store/index'

export default combineReducers({
    classify: classifyReducer,
    home: homeReducer
})

2、 子仓从创建

1、 子仓的解构

位置:在每一个需要请求数据的组件中创建

屏幕截图 2022-07-24 212853.png

index.js文件,将子仓的数据交给总仓

import reducer from "./reducer";
import * as actionCreators from './actionCreators'

export {
    reducer,
    actionCreators
}

actionCreators.js(通讯员)文件 将action(信息)用dispatch(发报员)方法交给reducer(参谋长)

import * as actionType from './constants'
import { getClassifyListRequest } from '@/api/request'

export const changeClassifyList = (data) => ({
    type: actionType.CHANGE_CLASSIFY_LIST,
    data: data
})
export const getClassifyList = () => {
    return (dispatch) => {
        getClassifyListRequest().then(data => {
            console.log(data,'-------------------')
            const action = changeClassifyList(data.classifylist)
                dispatch(action)
            console.log(action,'----')
        })
    }
}

reducer.js(参谋长) 文件

存放、修改状态\

注意:返回的新状态,与旧状态引用地址不同,为了方便溯源,也可以用 Object.assign()  方法,效果一样

import * as actionTypes from './constants'


const defaultState = {
    classifyList: []
}
export default (state = defaultState, action) => {
    // 通过 type 判断修改哪个数据
    switch(action.type) {
        case actionTypes.CHANGE_CLASSIFY_LIST:
            // 返回了新的状态
            return {
                ...state,
                classifyList: action.data
            }

        default:
            return state;
    }
}

constants.js 文件,存放 type 类型配置

export const CHANGE_CLASSIFY_LIST = 'CHANGE_CLASSIFY_LIST'

至此store文件就创建完成了,可以去Redux DevTools 查看数据

屏幕截图 2022-07-24 221104.png

屏幕截图 2022-07-24 221114.png

3、组件连接仓库

屏幕截图 2022-07-24 221354.png

4、classify组件的实现

1. 设置路由懒加载

import { useState, lazy } from 'react'
import { Routes, Route, Link} from 'react-router-dom'
import Home from '../pages/Home'
const Classify = lazy(() => import('../pages/Classify'))
const Shopcar = lazy(() => import('../pages/Shopcar'))
const Mine = lazy(() => import('../pages/Mine'))
const Search = lazy(() => import('../pages/GoSearch'))

const RoutesConfig = () => {

    return (
        <Routes>
            <Route path="/" element={<Home />}></Route>
            <Route path="/home" element={<Home />}></Route>
            <Route path="/classify" element={<Classify />}></Route>
            <Route path="/shopcar" element={<Shopcar />}></Route>
            <Route path="/mine" element={<Mine />}></Route>
            <Route path="/search" element={<Search />}></Route>
        </Routes>
    )
}

export default RoutesConfig

2. 组件功能实现

由功能可知这个组件主要就是要实现一个双联动效果

组件展示:

chrome-capture-2022-6-24.gif

部分代码:

1、实现双联动效果

  // 实现双联动效果
  const scrollToAnchor = (anchorName) => {
    if (anchorName) {
        let anchorElement = document.getElementById(anchorName)
        if (anchorElement) {
            anchorElement.scrollIntoView({
                block: 'start',
                behavior: 'smooth'
            })
        }
    }
}

2、 左边栏

// 左边栏
const renderInfo = () => {
  return classifyList.map(item => (
      <div className="left-data" key={item.id}>
          <a onClick={() => scrollToAnchor(item.id)}>
              <span>{item.title}</span>
          </a>
      </div>
  ))
}

3、 右边栏

// 右边栏
const renderSaleSlide = () => {
  return classifyList.map(item => {
    return (
      <div className="menu-box-detail" key={item.id}>
        <div className="menu-top">
          <div className="title-img-box">
            <img className="title-img-box_img" src={item.img} />
          </div>
          <div className="top-title" id={item.id}>
                {item.name}
            </div>
            <span>{item.description}</span>
          </div>
        <div className="menu-box">
          { item.phone && item.phone.map(element => {
            return (
            <><div className="top-title" key={element.title_id}>
                <span>{element.phone_title}</span>
              </div><div key={element.id} className="menu-detail">
                  <div className="menu-detail-box">
                    <div className="menu-item" key={element.id}>
                      <div className="img-bubble">
                        <div className="img-box">
                        {/* <LazyLoad 
                        placeholder={
                          <img width="100%" height="100%"
                              src={loading}
                          />}>       */}
                          <img className="sale-img" src={element.img} alt="" />
                          {/* </LazyLoad> */}
                        </div>
                      </div>
                      <section>
                        <p className="fooddetail-info">
                          <span>{element.name}</span>
                        </p>
                        <p className="fooddetail-sale">
                          <span>{element.introduce}</span>
                        </p>
                        <div className="fooddetails-space"></div>
                        <span className="sale_price">
                          <span>{element.price}</span>
                        </span>
                      </section>
                    </div>
                  </div>
                </div></>
            );
          })}
        </div>
      </div>
    );
  });
};

4、 组件调用方法

 return (
    <Wrapper>
      <Search/>
        <div className="sale-box">
            <div className="sale-main">
                <div className="sale-left">
                    <ul>
                        {renderInfo()}
                    </ul>
                </div>
                <div className="sale-detail-box">
                    <div className="sale-detail">
                        {renderSaleSlide()}
                    </div>
                </div>
            </div>
        </div>
    </Wrapper>
  )

配合组件连接Store仓库的方法就可以拿到数据就可以实现出双联动效果

这个组件可以进行封装以后复用

五、结语

这次项目的完成过程中,对于运用redux管理数据还是比较麻烦的,我在拿数据过程中大毛病没有,小毛病还是不断的,但是redux的编写还是有迹可循的,对着模板还是比较简单,配合多次练习越来越熟悉就好。

至此这个项目就结束了,虽然只实现了页面组件,交互方面还很粗糙,我会随着学习的越来越深入慢慢来丰富这个组件,争取在不久之后真的做到1:1复刻,感谢各位阅读了。最后麻烦大家动一动发财的小手戳一个小星星哦。

github [源码地址](wenhaolan1/OPPO-shop (github.com))

github pages

猜你喜欢

转载自juejin.im/post/7123954291152257037