手把手教你做:快手最新爆款“一甜相机”——Redux升级版(二)

前言

React组件化开发也学习了有一段时间了,前段时间在掘金上发布的一篇文章:手把手教你做:快手最新爆款“一甜相机”- 新手react开发必备项目 正是我react开发项目的一个试水篇章
一甜相机的模板页面还是个雏形,现在我结合这段时间所学的redux,将项目中的数据交给redux管理,优化了页面,同时新增了一些功能。本篇文章将围绕上一篇文章进行优化,如果看不明白的小伙伴可以移步上一篇哦~

页面优化部分

这个项目起步时,我的学习还不够到位,还有不少地方都比较粗糙,这篇文章的优化部分我将从页面自适应,路由,页面等方面进行。

优化项目

  • 自适应页面:上次项目开发时,使用单位均为px,在电脑上的移动端效果显示一切正常,但用到 gitPage 在手机端展示的时候,布局有一些混乱,增加页面自适应以解决这个问题
  • 路由的懒加载:按需加载
  • 导航栏优化:先前的页面导航栏切换均为tab切换,写起来过于繁琐,二级路由的使用能减少代码的繁琐
  • redux数据管理:项目中的大部分数据状态改为redux接管,方便管理
  • 搜索栏跳转页面优化:切入切出动画效果
  • 图片懒加载的实现:在学习了神三元的云音乐项目后,图片的懒加载被提上了日程,在图片还未加载成功时用一张默认图片代替,可以提高用户体验感

自适应页面

移动端的适应性与电脑端不同,使用px在电脑端布局看着是没有问题,但一但到了真正的移动端,由于手机型号的多变,页面局部也需要进行调整,px是绝对单位,无法根据页面大小进行相应调整,为了优化用户体验感,我采用rem来进行自适应布局。
在项目目录下新增一个public文件夹,里面js文件中的adapter.js来写页面自适应布局

rem :是一个相对单位,是指相对于根元素的字体大小的单位。这样就意味着,我们只需要在根元素确定一个px字号,则可以来算出元素的宽高。对于页面自适应来说,rem这个相对单位有很大的作用

页面自适应代码如下:这是一个通用样式,html 的font-size 被设为了16px,经过尝试,这个大小可以刚好使页面修改量为最少(单指我这个项目)。

var init = function () {
    var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
    if (clientWidth >= 640) {
      clientWidth = 640;
    }
    //设计稿为750px ,css则需/2,为375px
    var fontSize = 16 / 375 * clientWidth;
    document.documentElement.style.fontSize = fontSize + "px";
  }
  
  init();
  
  window.addEventListener("resize", init);
  
  //如果页面宽度超过640px,那么页面中的宽度恒为640px

  //否则页面中html 的font-size的大小为 16 *(当前宽度/375)

路由的懒加载

缺点: 上篇文章中与路由相关的组件都是直接导入的,整个网页打开时就默认加载所有网页,这样会拖延首屏加载时间,导致用户体验感不强。路由懒加载就是来解决这个问题的!

优化: 新增的路由懒加载lazy,顾名思义,就是只加载你当前点击的那个模块。按需去加载路由对应的资源,提高首屏加载速度
(tip:首页不用设置懒加载,而且一个页面加载过后再次访问不会重复加载)

import { useState, lazy} from 'react'
import { Routes, Route, Link,Navigate } from 'react-router-dom'  

import Tem from '../pages/Tem' //首屏不需要懒加载
const Vedio = lazy(() => import('../pages/Vedio'))
const Pic = lazy(() => import('../pages/Pic'))
const Kd = lazy(() => import('../pages/Kd'))
const Searchk = lazy(() => import('../pages/Searchk'))
const Tpmb = lazy(() => import('../pages/Tem/Tpmb'))
const Spmb= lazy(() => import('../pages/Tem/Spmb'))
const Login = lazy(() => import('../components/Login'))
const Shop = lazy(() => import('../components/Shop'))  
const Geren = lazy(() => import('../components/Geren'))
const Tpmbdetail = lazy(() => import('../pages/Tem/Tpmbdetail'))
const Spmbdetail = lazy(() => import('@/pages/Tem/Spmbdetail'))

const RouteConfig =() =>{
    return (
        <Routes>
        <Route path="/" element={<Navigate to ='/temp'/>} />
        <Route path="/temp" element={<Tem/>}>
           {/* 二级路由  */}   
            <Route path="/temp/tpmb" element={<Tpmb/>}></Route>
            <Route path="/temp/spmb" element={<Spmb/>}></Route>
        </Route>
        <Route path="/pz" element={<Pic/>}></Route>
        <Route path="/vedio" element={<Vedio/>}></Route>
        <Route path="/kd" element={<Kd/>}></Route>
        <Route path="/select" element={<Searchk/>} />
        <Route path="/login" element={<Login/>} />
        <Route path="/geren" element={<Geren/>} />
        <Route path="/shop" element={<Shop/>} />
        <Route path="/tpmbdetail/:id" element={<Tpmbdetail/>} />
        <Route path="/spmbdetail/:id" element={<Spmbdetail/>} />
      </Routes>
    )
}

export default RouteConfig

导航栏优化

1. 第一层导航栏:二级路由

优化: 双层Tab键切换还是显得有些繁琐,于是第一层导航栏则换成了页面的二级路由,整个页面由路由包裹着,使用路由进行页面跳转十分方便快捷
操作: 在模板页面中用<TpNav/> 占位,将需要用到的二级路由写进一个数组,再用map方法和Swiper将二级路由展现出来,二级路由下的页面则由 <Outlet/> 输出

代码仅展示部分:
增加二级路由

        <Route path="/temp" element={<Tem/>}>
           {/* 二级路由  */}   
            <Route path="/temp/tpmb" element={<Tpmb/>}></Route>
            <Route path="/temp/spmb" element={<Spmb/>}></Route>
        </Route>

实现二级路由

// 将二级路由写入数组中
 let TpNavs = [
        { id: 1, desc: '图片模板', path: 'tpmb'},
        { id: 2, desc: '视频模板', path: 'spmb'}
    ]
 // 使用map 和 Swiper 将二级路由展现出来
  <div className="navbar swiper-container">
                <div className="nav-box swiper-wrapper">
                {
                    TpNavs.map((item, index) => {
                        return (
                            <NavLink
                                index={index}
                                to={`/temp/${item.path}`}
                                key={item.id}
                                className="nav-item swiper-slide"
                            >
                            {item.desc}
                            </NavLink>
                        )
                    })
                }
                </div>
            </div>

2. 第二层导航:超长自动滑动导航切换

上次项目中导航栏的滑动主要是由css 样式实现的,且无法进行自动滑动,滑动过程中还有滑动条出现,体验感还有待加强。
优化:使用anted-mobile 组件库中 CapsuleTabs 组件,实现超长自动滑动,具体实现还需修改

ps:antd-mobile是由蚂蚁金融团队推出的一个开源的react组件库,相当于Weui 组件库,能提供许多便捷功能的组件,打造react项目方便快捷当属它!大家开发项目的时候可以去找找有没有便捷的组件~

要在 CapsuleTabs组件中实现tab键的切换,需要另外设置一个类名。用classnames设置active时的tab键值,就能实现tab键的切换了

注意事项--修改antd-mobile样式

antd-mobile样式有点难改,经过我多次尝试和查询网络,现在最有效的方法是使用antd-mobile中的组件都需要另建一个css文件,在页面源码中找到需要修改元素的类名,所有包含在组件中的样式都需要在css文件中写样式

<CapsuleTabs  defaultActiveKey='2'>
          <CapsuleTabs.Tab className={classnames({active:tab1 == "hot"},'cap')} title={<a   onClick={()=> changeTab1("hot")}>热门</a>} key='1'>
          </CapsuleTabs.Tab>
          <CapsuleTabs.Tab className={classnames({active:tab1 == "new"})} title={<a onClick={()=>changeTab1("new")}>最新</a>} key='2'>
          </CapsuleTabs.Tab>
          <CapsuleTabs.Tab className={classnames({active:tab1 == "biye"})} title={<a  onClick={()=>changeTab1("biye")}>毕业季</a>} key='3'>
          </CapsuleTabs.Tab>
          <CapsuleTabs.Tab  className={classnames({active:tab1 == "summy"})} title={<a onClick={()=>changeTab1("summy")}>夏日</a>} key='4'>
          </CapsuleTabs.Tab>
          <CapsuleTabs.Tab className={classnames({active:tab1 == "kuai"})} title={<a  onClick={()=>changeTab1("kuai")}>快手爆款</a>} key='5'>

          </CapsuleTabs.Tab>
          <CapsuleTabs.Tab className={classnames({active:tab1 == "hbizhi"})} title={<a  onClick={()=>changeTab1("bizhi")}>壁纸</a>} key='6'>

          </CapsuleTabs.Tab>
          <CapsuleTabs.Tab className={classnames({active:tab1 == "pai"},'cap')} title={<a  onClick={()=>changeTab1("pai")}>拍立得</a>} key='7'>

          </CapsuleTabs.Tab>
        </CapsuleTabs>

修改样式后实现效果

chrome-capture-2022-6-13 (1).gif

redux数据管理

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
随着项目页面的增加,状态变得越来越复杂,

状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,页面的变换也有可能会引起状态的变化;
当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪 ,而redux可以解决这个问题——redux能够 集中式管理(读/写) react 应用中多个组件共享状态

Redux 可以用这三个基本原则来描述:
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。 State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers

  • store

store 就是保存数据的地方,它相当是一个容器。整个应用只能有一个 store。store 就是将 state、action 与 reducer 联系在一起的一个对象,在这个项目中,在一个总store 下还有两个子store掌管着数据状态

  1. 创建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   
  • redux-thunk 是一个比较流行的 redux 异步 action 中间件,比如 action 中有通过 axios通用远程 API 这些场景,那么就应该使用 redux-thunk 了。redux-thunk 帮助你统一了异步和同步 action 的调用方式,把异步过程放在 action 级别解决,对 component 没有影响。

2.汇总reducer
这个项目中有两个页面需要进行数据渲染,为了方便管理,我分别创建了两个子store,在reducer文件中将两个子store的reducer引入合并成总的reduce文件

import { combineReducers } from 'redux'   
import { reducer as TemReducer} from '@/pages/Tem/store/index'
import { reducer as GerenReducer } from '@/components/Geren/store/index'

export default combineReducers({
    Tem:TemReducer,
    Geren:GerenReducer
})
  • actionCreators

所有的数据都需要通过派发(dispatch)action 来更新,action 也是一个普通的 js 对象,这个对象包含两部分:更新的 typedata ,拉取数据需要在获取数据后dispatch (action)
仅展示部分代码:

export const changeBannerList = (data) =>({
    type:actionTypes.CHANGE_BANNER,
    data
})

export const getBannerList = () =>{
    return (dispatch) => {
        getBanners()
        // console.log(data)
            .then(data => {
                const action = changeBannerList(data)
                dispatch(action)
                dispatch(changeEnterLoading(false))
        })
    }
}

  • reducer

将 state 和 action 联系在一起,也就是根据旧的 state 和 action, 产生新的state 的纯函数

const reducer= (state = defaultState,action)=>{
    switch (action.type){
        case actionTypes.CHANGE_BANNER:
            return  {
                ...state,
                bannerList:action.data
            }
      ...
            default:
                return state
    }
    
}
  • constants

本文件用于管理所有actiontype,方便维护

export const CHANGE_BANNER = 'CHANGE_BANNER'
export const CHANGE_TPMB_LIST = 'CHANGE_TPMB_LIST'  
export const CHANGE_SPMB_LIST = 'CHANGE_SPMB_LIST'  
export const CHANGE_ENTERLOADING = 'CHANGE_ENTERLOADING'
export const CHANGE_ALBUM = 'CHANGE_ALBUM'
export const CHANGE_TPMBLIST_ID = 'CHANGE_TPMBLIST_ID'
export const CHANGE_TPMB_STAR = 'CHANGE_TPMB_STAR'
export const CHANGE_SPMB_STAR = 'CHANGE_SPMB_STAR'

搜索栏跳转页面优化

通过学习神三元的云音乐项目,我get到了搜索栏切入切出的动画效果,于是,我将它运用到了我的搜索栏中,优化页面跳转

具体实现效果如下:
操作: 点击搜索栏,搜索页面会从右侧平移过来,点击取消,动画消失

24.gif 代码实现:
安装依赖-- React过渡动画

在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验。 React 可以为我们提供react-transition-group

这个库可以帮助我们方便的实现组件的 入场 和 离场 动画,使用时需要进行额外的安装:

npm i react-transition-group

引入CSSTransition--- 在前端开发中,通常使用CSSTransition来完成过渡动画效果

import { CSSTransition } from 'react-transition-group'

<CSSTransition   
        in={show} //用于判断是否出现的状态
        timeout={300} // 动画持续时间
        appear={true}   
        classNames="fly"  // classNames值,防止重复
        unmountOnExit //元素退场时,自动将DOM删除
        onExited={() =>{
          navigate(-1)
        }}
    >
    ...搜索页面内容
    <CSSTransition />

CSSTransition的动画效果主要由css样式来实现

  • 它们有三种状态,需要定义对应的CSS样式:
  • 第一类,开始状态:对于的类是-appear、-enter、exit;
  • 第二类:执行动画:对应的类是-appear-active、-enter-active、-exit-active;
  • 第三类:执行结束:对应的类是-appear-done、-enter-done、-exit-done;
//搜索页面实现动画效果的css
export const Container = styled.div`
    position: fixed;
    top: 0;   
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    z-index:100;
    overflow: hidden;
    background: #f2f3f4;
    transform-origin:right bottom;  
    &.fly-enter,&.fly-appear {
        opacity:0;
        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);
        }
`

图片懒加载的实现

在学习了神三元的云音乐项目后,我又get到了一项新技能——图片的懒加载 ,利用Scroll组件和react-lazyload 中的Lazyload 组件实现图片下滑后的懒加载

  • onScroll 事件:在页面滚动时触发此事件,forceCheck 是从react-Lazyload中解构出来的函数,实现图片移动到视口时进行加载,<Lazyload/> 组件为数据提供了一个默认图片,还未到达视口的图片由默认图片替代,等图片滚动到视口时再进行加载,优化了用户体验感。
// Scroll 
<Scroll onScroll ={forceCheck}>
     <TpmbList  tpmbda={tpmbda}/> 
     </Scroll>
//Lazyload 
<ListWrapper>
     {tpmbda.map(item => 
    <Link to ={`/tpmbdetail/${item.id}`} key = {item.id}>
      <List key = {item.id}>
        <div className="img_wrapper">
        <Lazyload placeholder={<img 
        width="100%" height="100%"
        src={tp}/>
      }>
       <img src={item.img} alt="" />
        </Lazyload>
        </div>
       
        
        <p className="get" onClick={() => {
              Toast.show({
                icon: 'loading',
                content: '加载中…',
              })
            }}>Get 同款</p>
      <p className='title'>{item.title}</p>
     </List></Link>)}
    </ListWrapper>

具体实现效果为:

chrome-capture-2022-6-14 (2).gif

页面新增功能

  • 简易版登录功能:实现登录页面跳转用户页面
  • 图片导航实现相册选取
  • 图片细节页面的实现:模板的下滑,用户的关注收藏
  • 视频模板细节页面的实现:视频的自动播放,收藏数等

简易版登录功能

实现效果:

7.gif

由于没有后端,登录功能只能做一个简易版的,主要是使用变量和localstorage实现

  1. 登录功能,在默认头像上添加点击事件,引入antd-mobile中的Mask组件,制作登录弹窗,通过visible变量来控制弹窗的出现和消失
{/* 遮罩层 */}
             <Mask visible={visible}  >
             <div className="content">  
                     <p>登录一甜相机</p>                        
                      <p> get专属于你的美颜相机!</p>
              <div className='btns'>
                <span onClick={() => setVisible(false)}>取消</span>
                <span className='login' onClick={() => (setShowlogo(false))}><Link to="/login" > 登录 </Link></span>
              </div>
                 </div>
              </Mask>   
  1. 点击登录则跳转登录页面,下面的复选框check值由变量控制,在登录按钮中添加点击事件,通过判断check的值来判断页面是否能成功登录,check为false则跳出弹窗提示进行勾选,若为true则跳转页面
//判断复选框是否被勾选
  const setSelect = () =>{
    if(check){
      navigate('/temp/tpmb')
    }else{
      Modal.alert({
        content: '请勾选同意后再进行登录'
      })
    }
  }
  1. 实现默认头像图片和用户头像转换:在登录页面中添加一个localstorage变量

window.localStorage.showuser = 'showuser'

点击登录,在页面跳转成功的页面上添加一个判断函数 :

if(!window.localStorage.getItem('showuser')){

window.localStorage.setItem('showuser','');
}else{}

 { showuser && user.map(item => {
           return (
          <span className="im" key={item.id}><Link to="/geren"><img src={item.img}/></Link></span>  
      )
    }) }

通过判断showuser的值来实现用户头像的替换,由于localstorage存储的值均为string类型,要使showuser展示出boolean类型,为false则设为空值,即解决了头像无法转换的问题

图片导航实现相册选取

和上面登录功能一样,照片拉取也是关于后端的功能,于是我就做了一个双层弹窗转换相册的功能

具体实现效果:

8(1).gif

图片导航采取的是双层嵌套弹出层的组件,也是在antd-mobile组件库中引入的

双层弹出层

<Popup
              visible={visible7}
              showCloseButton
              onClose={() => {
                setVisible7(false)
              }}
              bodyStyle={{
                borderTopLeftRadius: '1px',
                borderTopRightRadius: '1px',
                minHeight: '100vh',
              }}
            >
               
              {mockContent}
            </Popup>
              </li>
              <Popup
          visible={visible}
          bodyStyle={{ height: '90vh' }}
        >
          <div style={{ padding: '24px' }} className="album" >
            {renderAlbums()}
          </div>
        </Popup>

相册选取功能

使用函数将相册列表渲染上去,点击列表选择相册则转至指定相册
<span>{albumName?albumName:''}</span>

  const renderAlbums = () => {
    return albumList.map(({id,nm,img}) => {
        return <Link 
        className="album_name"
        to={{
          search:`name= ${nm}`  // ? 后面的参数  0
        }}
        onClick={() => {
          setVisible(false)
        }}
        key= {id}>
         <img src={img} /> 
            {nm}
        </Link>
    })
  }

图片细节页面实现

这个功能实现的是由首页的图片模板点进去的细节页面,这个页面分为三个步骤去实现:

  1. 使用useParams 获取这个页面图片的id值,使用slice() 这个api将图片列表数据从此id开始截取 ,并使用map函数将图片模板渲染至页面上

const { id } = useParams() //获取页面id值
setData1(tpmbList.slice(id-1)) // id从0开始,即id-1为本页面id值

 const renderTpmbdetail =() => {
    return data1.map(item =>{
      return (
        <List key = {item.id}>
             <NavBar>
          <CloseOutline   onClick={() => navigate(-1)} className="close"/>
              {item.artist}<span className='gz' onClick={() =>setAttention(item.id)}>{ (item.attention)?'已关注':'关注'}</span>
             {/* { console.log(item.attention)} */}
              <SendOutline className='share'  onClick={() => {
                setVisible5(true)
              }}/>
            </NavBar>
            <div className='dw'>
            <img src={item.img} alt="" />
          <p>
            <span className='title'>{item.title}</span>
            <span className='sc'>收藏{item.star1 ? item.star : item.star-1}</span>
          </p>
            </div>
            <div className='caozuo'>
              <span className='star'><Rate className='star_a' count ={1} 
              style={{
                '--active-color': '#ea84ae',
                '--inactive-color':'#fec7df'
              }}
              onChange={() =>setStar(item.id)}
              /></span>
              <span className='zi'>Get同款</span>
            </div>
        </List>
      )
    })
  }

具体效果为:

9.gif
2. 实现关注作者功能 :关注功能实现要将id传进数据列表中,将attention 的值改为相反值,再将数据重传,重新渲染

  • 在函数中触发点击事件,将id值作为参数传入 ,在函数中运行dispatch函数后再判断
{item.artist}<span className='gz' onClick={() =>setAttention(item.id)}>{ (item.attention)?'已关注':'关注'}</span>

const setAttention = (id) =>{
  changeTpmbListByIdDispatch(id)
  setData1(tpmbList.slice(id-1))
}
  • 在页面上dispatch一下修改关注对象,将id值作为data传入
const mapDispatchToProps =( dispatch ) =>{
  return {
      changeTpmbListByIdDispatch(data) {
        dispatch(actionCreators.changeTpmbListById(data))
      }
  }
}
  • reducer里面进行数据修改
仅展示部分代码:
// 根据id修改attention  
const changeTpmbById = (list,id) =>{
  console.log(list)
  console.log(id)
  let index = list.findIndex(data => id == data.id);
  list[index].attention = !list[index].attention;
  return list;
}
// 根据action的值返回数据
case actionTypes.CHANGE_TPMBLIST_ID:
          // console.log(action.data)  通过id改变attention状态  
               return {
                ...state,
                tpmbList:changeTpmbById(Object.assign([],state.tpmbList),action.data)
               }

具体实现:

10(2).gif

3.实现收藏功能: 与关注相仿,收藏功能也需要将id传入数据列表中进行查找到相应的了列表项,将star1的值改为true,再重新渲染数据

  • 在Rate中触发onChange事件,将id值作为参数传入,在函数中运行dispatch函数
const setStar = (id) => {
  changeTpmbListStar(id)
  // changetStarDispatch(id)
  setData1(tpmbList.slice(id-1))
}
//onChange事件  
<Rate className='star_a' count ={1} 
              style={{
                '--active-color': '#ea84ae',
                '--inactive-color':'#fec7df'
              }}
              onChange={() =>setStar(item.id)}
              /></span>
  • mapDispatchToProps 中创建一个有参数的dispatch函数
const mapDispatchToProps =( dispatch ) =>{
  return {
     
      changeTpmbListStar(data){
        dispatch(actionCreators.changeTpmbListStar(data))
      }
  }
  • reducer中进行数据的查询修改
// 修改函数   
const changeTpmbStar =(list,id) => {
  let ind = list.findIndex(data => id == data.id);
  list[ind].star1 = !list[ind].star1;
  return list
}
//根据action的值调用修改函数
   case actionTypes.CHANGE_TPMB_STAR:
               return {
                ...state,
                tpmbList:changeTpmbStar(Object.assign([],state.tpmbList),action.data)
               }

具体实现为:

chrome-capture-2022-6-14.gif

视频细节页面实现

上篇文章中这样页面没有用真正的视频,仅仅用图片进行了占位,这次我准备好视频了,并且实现了视频的播放,暂停等功能,视频细节页面也与图片细节页面相仿,能从本视频id开始一直向下滑,实现收藏功能

  1. 实现视频的占位和播放
// 点击实现视频的暂停播放
  const videoRef = useRef(null)  
  const [play,setPlay] = useState(false)  
  const onVideo = () =>{
    if(play) {
      videoRef.current.pause();
      setPlay(false)
    }else{
      videoRef.current.play();
      setPlay(true)
    }
  }
  //放置视频     
  <video 
            controls="controls" //进度条
            src={item.videos}
            onClick={onVideo} 
            ref={videoRef}
            loop
            autoPlay="autoplay" //自动播放
            muted="true"  //静音
            /> 
  1. 实现收藏功能

过程与图片模板收藏相似,也是将id传入数据列表,在进行修改数据后返回

  • mapDispatchToPropsdispatch一下action
const mapDispatchToProps =( dispatch ) =>{
  return {
      changeSpmbListStar(data){
        dispatch(actionCreators.changeSpmbListStar(data))
      }
  }
}
  • 在reducer中根据id进行数据的修改
// 修改收藏star1的值   
const changeSpmbStar =(list,id) => {
  let ind = list.findIndex(data => id == data.id);
  list[ind].star2 = !list[ind].star2;
  return list
}     
// 调用函数
  case actionTypes.CHANGE_SPMB_STAR:
                return {
                ...state,
                 spmbList:changeSpmbStar(Object.assign([],state.spmbList),action.data) //浅拷贝
                }

具体效果为:

12.gif

由于和这个相机相关的视频链接因为跨域容易被禁止,所有这个项目的视频都是下载至本地来进行展示,也因为这个原因导致gitPage不能实现,我就给大家录了个较为完整的视频,大家将就看一下吧~

26(1).gif

26(1).gif

结束语

经过这段时间的修改和完善,项目的完善度也是更近一步了!还是有很多不足,和功能还未实现,我将继续努力,继续完善这个项目,大家有什么建议也可以在评论区告诉我,我们下期再见~
(ps:本篇文章图源网络,如有侵权,请联系我删除)

项目源码地址:react-camera.github.io/react-camer…

猜你喜欢

转载自juejin.im/post/7120441628170387486
今日推荐