Третья пуля 🎉 React Hooks

JavaScript React.js
Третья пуля 🎉 React Hooks

предисловие

После некоторого периода изучения ведро семейства React также знакомо, поэтому я продолжу писать об увлечении вещами с хуками react + Друзья, пожалуйста, лайкните и подпишитесь🤗.

0.gif

Введение в проект

  • Используйте Redux для централизованного управления данными, Mockjs имитирует внутренний интерфейс данных.
  • Придерживайтесь идеи MVVM, компонентизации и модульности и пишите страницы с чисто рукописными функциональными компонентами.
  • Используйте Immutable для сохранения данных и оптимизации рендеринга компонентов.
  • Написание стилей с помощью styled-components
  • React-Router v6 пишет маршрутизацию, полностью используя стиль программирования Hooks.

Раздел классификации

0001.gif

(Примечание: поскольку данные передаются только по одежде, это нормальная работа, если другие элементы в меню первого уровня не отображаются нормально, а в конце текста есть онлайн-адрес)

  • использоватьbetter-scrollСоздайте базовый скользящий компонент, используйте облачный музыкальный проект NetEase компании Sanyuan и создайте компоненты для переключения горизонтальной навигации и списка продуктов на вертикальном экране.
  • определениеScrollОбязательные параметры
import PropTypes from "prop-types"

Scroll.defaultProps = {
  direction: "vertical",
  click: true,
  refresh: true,
  onScroll:null,
  pullUpLoading: false,
  pullDownLoading: false,
  pullUp: null,
  pullDown: null,
  bounceTop: true,
  bounceBottom: true
};

Scroll.propTypes = {
  direction: PropTypes.oneOf(['vertical', 'horizental']), // 滚动的方向
  refresh: PropTypes.bool, // 是否刷新
  onScroll: PropTypes.func, // 滑动触发的回调函数
  pullUp: PropTypes.func, // 上拉加载逻辑
  pullDown: PropTypes.func, // 下拉加载逻辑
  pullUpLoading: PropTypes.bool, // 是否显示上拉 loading 动画
  pullDownLoading: PropTypes.bool, // 是否显示下拉 loading 动画
  bounceTop: PropTypes.bool, //是否支持向上吸顶
  bounceBottom: PropTypes.bool //是否支持向下吸顶
};
  • Обработка инкапсуляцииScrollкомпоненты
  import React, { forwardRef, useState,useEffect, useRef, useImperativeHandle, useMemo } from "react"
  import BScroll from "better-scroll"
  import { debounce } from "../../utils/uiOptimization";

  const [bScroll, setBScroll] = useState()
  const scrollContaninerRef = useRef()
  const { direction, click, refresh, pullUpLoading, pullDownLoading, bounceTop, bounceBottom } = props
  const { pullUp, pullDown, onScroll } = props
  // 防抖
  let pullUpDebounce = useMemo(() => {
    return debounce(pullUp, 500)
  }, [pullUp])
  
  let pullDownDebounce = useMemo(() => {
    return debounce(pullDown, 500)
  }, [pullDown])
  // 创建 better-scroll 实例
  useEffect(() => {
    const scroll = new BScroll(scrollContaninerRef.current, {
      scrollX: direction === "horizental",
      scrollY: direction === "vertical",
      probeType: 3,
      click: click,
      bounce:{
        top: bounceTop,
        bottom: bounceBottom
      }
    });
    setBScroll(scroll)
    return () => {
      setBScroll(null)
    }
  }, [])
  // 实例绑定 scroll 事件
  useEffect(() => {
    if(!bScroll || !onScroll) return
    bScroll.on('scroll', onScroll)
    return () => {
      bScroll.off('scroll', onScroll)
    }
  }, [onScroll, bScroll])
  // 上拉判断
  useEffect(() => {
    if(!bScroll || !pullUp) return;
    const handlePullUp = () => {
      //判断是否滑动到了底部
      if(bScroll.y <= bScroll.maxScrollY + 100){
        pullUpDebounce()
      }
    };
    bScroll.on('scrollEnd', handlePullUp)
    return () => {
      bScroll.off('scrollEnd', handlePullUp)
    }
  }, [pullUp, pullUpDebounce, bScroll])
  // 下拉判断
  useEffect(() => {
    if(!bScroll || !pullDown) return;
    const handlePullDown = (pos) => {
      //判断用户的下拉动作
      if(pos.y > 50) {
        pullDownDebounce()
      }
    };
    bScroll.on('touchEnd', handlePullDown)
    return () => {
      bScroll.off('touchEnd', handlePullDown)
    }
  }, [pullDown, pullDownDebounce, bScroll])
  // 刷新实例 防止无法滑动
  useEffect(() => {
    if(refresh && bScroll){
      bScroll.refresh()
    }
  })
  // 刷新组件
  useImperativeHandle(ref, () => ({
    // 暴露 refresh 方法
    refresh() {
      if(bScroll) {
        bScroll.refresh();
        bScroll.scrollTo(0, 0);
      }
    },
    // 暴露 getBScroll 方法,提供 bs 实例
    getBScroll() {
      if(bScroll) {
        return bScroll;
      }
    }
  }));
  • Страница категории выглядит как простое трехуровневое меню? Правильно, это простое трехуровневое меню.
  • Строительство маршрутизации
import React, { lazy, Suspense } from 'react'

const KindComponent = lazy(() => import("../pages/kind"))

export default [
  {
    path: "/",
    element: <HomeLayout />,
    children: [
        ...
        {
            path: "/kind",
            element: <Suspense fallback={null}><KindComponent></KindComponent></Suspense>
        },
        ]
  }
]
  • стилизованные компонентыLcontentа такжеNavContainerи компоненты пользовательского интерфейсаColumnСформируйте меню первого уровня.
<Lcontent>
   <NavContainer>
      <Column list={kindTypes} handleClick={handleUpdateCatetory} oldVal={category}/>
   </NavContainer>
</Lcontent>
<Horizen2 list={item.title} handleClick={handleUpdetaList} oldVal={category2} />
  • использоватьuseRefЗапустите Dom, чтобы получить высоту или ширину каждого элемента для накопления и инициализации высоты или ширины родительского элемента.
useEffect(() => {
    let categoryDOM = Category.current
    let tagElems = categoryDOM.querySelectorAll("span")
    let totalWidth = 0
    Array.from(tagElems).forEach(ele => {
      totalWidth += ele.offsetWidth
    });
    totalWidth += 120
    categoryDOM.style.width = `${totalWidth}px`
  }, [])

Раздел поиска

0003.gif

  • Строительство маршрутизации
import React, { lazy, Suspense } from 'react'

const SearchComponent = lazy(() => import("../pages/search"))

export default [
  {
    path: "/",
    element: <HomeLayout />,
    children: [
        ...
        {
            path: "/search",
            element: <Suspense fallback={null}><SearchComponent></SearchComponent></Suspense>
        },
        ]
  }
]
  • Компоненты пользовательского интерфейса окна поискаSearchBox
// useRef 监听输入
const queryRef = useRef()
<input ref={queryRef} className="box" placeholder="输入商品名" value={query} onChange={handleChange}/>

// 光标聚焦
useEffect(() => {
    queryRef.current.focus()
}, [])

//  防抖  缓存
import { debounce } from '../../utils/uiOptimization'

let handleQueryDebounce = useMemo(() => {
    return debounce(handleQuery, 500)
}, [handleQuery]);
useEffect(() => {
    handleQueryDebounce(query)
}, [query])

Разложить поиск

  • axiosподготовка запроса
export const getHotKeyWordsRequest = () => {
  return axiosInstance.get(`/search/hot`);
}
export const getResultList = (query) => {
  return axiosInstance.get(`/search/keywords=${query}`)
}
  • mockзапрос на перехват
import Mock from "mockjs";
import { hotKeyWords } from './hot';
import shopAPI from './shop';

// 匹配接口 api  拦截请求
Mock.mock(/\/search\/hot/, "get", hotKeyWords);

Mock.mock(/\/search\/keywords=.+/, "get", (options) => {
    let keyword = options.url.split('=')[1]  
    return shopAPI.shops().data.items.filter((item) => {
        return item.title.indexOf(keyword) > 0
    })
})

разработка редукционного слоя

  • инициализацияstate
const defaultState = ({
  hotKeyWords: [], // 热门关键词列表
  enterLoading: false,
  resultList: []
})
  • определениеconstants
export const SET_HOT_KEYWRODS = "search/SET_HOT_KEYWRODS"
export const SET_ENTER_LOADING = 'search/SET_ENTER_LOADING'
export const SET_RESULT_LIST = 'search/SET_RESULT_LIST'
  • определениеreducerфункция
import { produce } from 'immer'

export const searchReducer = produce((state, action) => {
    switch(action.type) {
        case actionTypes.SET_HOT_KEYWRODS:
            state.hotKeyWords = action.data
            break;
        case actionTypes.SET_ENTER_LOADING:
            state.enterLoading = action.data
            break;
        case actionTypes.SET_RESULT_LIST:
            state.resultList = action.data
            break;
    }
}, defaultState)
  • Читатели могутactionCreators.jsконкретное представление файлаactionфункции, которые здесь не перечислены.
  • Экспорт связанных переменных
import { searchReducer } from './reducer'
import * as actionCreators from './actionCreators'
import * as constants from './constants'

export { searchReducer, actionCreators, constants }
  • reducerзарегистрировать глобальныйstore
import { combineReducers } from "redux";
import { searchReducer } from '../pages/search/store';

export default combineReducers({
  search: searchReducer,
});
  • Сделайте эти приготовления, и тогда вы можете официально подключитьсяreduxЛа!
// index.jsx
import { useDispatch, useSelector } from 'react-redux'

const { hotKeyWords, enterLoading, resultList } = useSelector((state) => ({
    hotKeyWords: state.search.hotKeyWords,
    enterLoading: state.search.enterLoading,
    resultList: state.search.resultList
}))
const dispatch = useDispatch()

const getHotKeyWordsDataDispatch = () => {
    dispatch(actionTypes.getHotKeyWords())
}
const getResultListDispatch = (q) => {
    dispatch(actionTypes.getResultGoodList(q))
}
  • Когда поле поиска пусто, отображать список популярных слов
useEffect (() => {
    setShow (true)
    if (!hotKeyWords.length) {
    getHotKeyWordsDataDispatch()
    }
}, [])
  • Нажмите на горячее слово, чтобы отправить запрос на отображение результатов поиска
const handleQuery = (q) => {
    setQuery (q);
    if(!q) return;
    dispatch(actionTypes.changeEnterLoading(true));
    getResultListDispatch(q);
}

раздел комментариев

0002.gif

  • использоватьcss @keyframesПравила реализуют всплывающий слой
export const CommentsContainer = styled.div`
  position: fixed;
  height: 70vh;
  width: 100vw;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 2000;
  background: ${style["default-color"]};
  animation: Popup .4s;
  padding: 60px 0;
  @keyframes Popup {
    0% {
      transform: translate3d(0, 100%, 0);
    }
    100% {
      transform: none;
    }
  }
  ...
`

Обзор реализации Moment.js + lokijs

  • новыйdatabaseПапка, добавить создать базу данных, коллекцию
// index.js
import Loki from 'lokijs'

export const db = new Loki('goods', {
    autoload: true,
    autoloadCallback: databaseInitialize,
    autosave: true,
    autosaveInterval: 3000,
    persistenceMethod: "localStorage"
})

// 创建集合 
function databaseInitialize() {
    const comments = db.getCollection('comments')
    if (comments == null) {
        db.addCollection('comments')
    }
}

export function loadCollection(collection) {
    return new Promise(resolve => {
        db.loadDatabase({}, () => {
            const _collection = db.getCollection(collection)
            resolve(_collection)
        })
    })
}
  • С базами данных и коллекциями комментарии не правши?
import moment from 'moment'

// 需引入moment.locale()文件才能转换日期,完整代码就不列出

const [data, setData] = useState([])
const [query, setQuery] = useState('')
const queryRef = useRef()

// 获取输入框的值
const handleChange = () => {
    let newValue = queryRef.current.value
    if (newValue.trim() != '') {
      setQuery(newValue);
    }
}
  • Инициализировать данные списка комментариев
useEffect(() => {
    loadCollection('comments')
      .then((collection) => {
        const entities = collection.chain()
          .find()
          .simplesort('$loki', 'isdesc')
          .data()
        setData(entities)
      })
}, [])
  • Добавить комментарий, чтобы вставить коллекцию
const createComment = (query) => {
    if (query.trim() != '') {
      setData([...data, { body: query, meta: {
        created: Date.now()
      }}])
      loadCollection('comments')
      .then((collection) => {
        collection.insert([
          {
            body: query
          }
        ])
      })
      .then(setQuery(''))
    }
}
  • Отображение списка комментариев
<Scroll direction={"vertical"}>
      <div className="comments-box">
          {
            data.map((item, index) => 
              <CommentsBox key={index}>
                <div className="comment">
                  {item.body}  
                </div>
                <div className="time">
                  {moment(item.meta.created).fromNow()}
                </div>
              </CommentsBox>)
          }
      </div>
</Scroll>

трудности, основные моменты

  • представлятьprop-typesБиблиотека выполняет квалификацию типов при обработке потоков данных.
  • используется при построении маршрутаReact.lazyа такжеReact.SuspenseВыполните оптимизацию производительности, внедрите сегментацию кода и улучшите работу.
  • использоватьReact.memo,useMemo,useCallbackЧтобы оптимизировать отрисовку компонентов, используйтеimmerСохранение данных, оптимизация рендеринга компонентов
  • Комментарии обновляются в режиме реального времени.MVVM
const createComment = (query) => {
    if (query.trim() != '') {
    // 前端数据更新
      setData([...data, { body: query, meta: {
        created: Date.now()
      }}])
      // 插入数据库 数据更新
      loadCollection('comments')
      .then((collection) => {
        collection.insert([
          {
            body: query
          }
        ])
      })
      .then(setQuery(''))
    }
  }
  • better-scrollПринцип заключается в том, что ширина или высота родителя фиксированы, а дочерний элемент прокручивается, если он превышает длину экрана, и его экземпляр действует только на первый дочерний элемент.
  • Используется при переходе страницыreact-transition-groupДелайте анимационные переходы

исходный код

1.gif