本节代码对应 GitHub 分支: chapter4

仓库传送门 (opens new window)

本节最终效果如下所示:

img

# 接受参数

在 baseUI 文件夹下新建 horizen-item 目录,接着新建 index.js。

首先分析这个基础组件接受哪些参数,

import React, { useState, useRef, useEffect, memo } from 'react';
import styled from'styled-components';
import Scroll from '../scroll/index'
import { PropTypes } from 'prop-types';
import style from '../../assets/global-style';

function Horizen (props) {
  return (
    // 暂时省略
  )
}

// 首先考虑接受的参数
//list 为接受的列表数据
//oldVal 为当前的 item 值
//title 为列表左边的标题
//handleClick 为点击不同的 item 执行的方法
Horizen.defaultProps = {
  list: [],
  oldVal: '',
  title: '',
  handleClick: null
};

Horizen.propTypes = {
  list: PropTypes.array,
  oldVal: PropTypes.string,
  title: PropTypes.string,
  handleClick: PropTypes.func
};
export default memo (Horizen);

现在,来把 props 对象进行解构赋值,

const { list, oldVal, title } = props;
const { handleClick } = props;

返回的 JSX 代码为:

return (
  <Scroll direction={"horizental"}>
    <div>
      <List>
        <span>{title}</span>
        {
          list.map ((item) => {
            return (
              <ListItem
                key={item.key}
                className={`${oldVal === item.key ? 'selected': ''}`}
                onClick={() => handleClick (item.key)}>
                  {item.name}
              </ListItem>
            )
          })
        }
      </List>
    </div>
  </Scroll>
);

样式代码:

// 由于基础组件样式较少,直接写在 index.js 中
const List = styled.div`
  display: flex;
  align-items: center;
  height: 30px;
  overflow: hidden;
  >span:first-of-type {
    display: block;
    flex: 0 0 auto;
    padding: 5px 0;
    margin-right: 5px;
    color: grey;
    font-size: ${style ["font-size-m"]};
    vertical-align: middle;
  }
`
const ListItem = styled.span`
  flex: 0 0 auto;
  font-size: ${style ["font-size-m"]};
  padding: 5px 8px;
  border-radius: 10px;
  &.selected {
    color: ${style ["theme-color"]};
    border: 1px solid ${style ["theme-color"]};
    opacity: 0.8;
  }
`

现在大家还看不到效果,可能会有些慌张,没关系,我们现在就把这个组件进入到页面中试一试。

# 载入页面

进入到 application/Singers/index.js 中,代码如下:

import React from 'react';
import Horizen from '../../baseUI/horizen-item';
import { categoryTypes } from '../../api/config';

function Singers () {
  return (
    <Horizen list={categoryTypes} title={"分类 (默认热门):"}></Horizen>
  )
}

export default React.memo (Singers);

分类数据在 api/config.js 中,但现在还没定义,现在在这个文件中添加以下代码:

// 歌手种类
export const categoryTypes = [{
  name: "华语男",
  key: "1001"
},
{
  name: "华语女",
  key: "1002"
},
{
  name: "华语组合",
  key: "1003"
},
{
  name: "欧美男",
  key: "2001"
},
{
  name: "欧美女",
  key: "2002"
},
{
  name: "欧美组合",
  key: "2003"
},
{
  name: "日本男",
  key: "6001"
},
{
  name: "日本女",
  key: "6002"
},
{
  name: "日本组合",
  key: "6003"
},
{
  name: "韩国男",
  key: "7001"
},
{
  name: "韩国女",
  key: "7002"
},
{
  name: "韩国组合",
  key: "7003"
},
{
  name: "其他男歌手",
  key: "4001"
},
{
  name: "其他女歌手",
  key: "4002"
},
{
  name: "其他组合",
  key: "4003"
},
];

// 歌手首字母
export const alphaTypes = [{
    key: "A",
    name: "A"
  },
  {
    key: "B",
    name: "B"
  },
  {
    key: "C",
    name: "C"
  },
  {
    key: "D",
    name: "D"
  },
  {
    key: "E",
    name: "E"
  },
  {
    key: "F",
    name: "F"
  },
  {
    key: "G",
    name: "G"
  },
  {
    key: "H",
    name: "H"
  },
  {
    key: "I",
    name: "I"
  },
  {
    key: "J",
    name: "J"
  },
  {
    key: "K",
    name: "K"
  },
  {
    key: "L",
    name: "L"
  },
  {
    key: "M",
    name: "M"
  },
  {
    key: "N",
    name: "N"
  },
  {
    key: "O",
    name: "O"
  },
  {
    key: "P",
    name: "P"
  },
  {
    key: "Q",
    name: "Q"
  },
  {
    key: "R",
    name: "R"
  },
  {
    key: "S",
    name: "S"
  },
  {
    key: "T",
    name: "T"
  },
  {
    key: "U",
    name: "U"
  },
  {
    key: "V",
    name: "V"
  },
  {
    key: "W",
    name: "W"
  },
  {
    key: "X",
    name: "X"
  },
  {
    key: "Y",
    name: "Y"
  },
  {
    key: "Z",
    name: "Z"
  }
];

# 解决滚动问题

启动项目,进入歌手列表页后,你发现这个横向列表并不能滚动,我们再回顾下 better-scroll 的 (横向) 滚动原理,首先外面容器要宽度固定,其次内容宽度要大于容器宽度。

因此目前存在两个问题:

  1. 外部容器未限定宽度,也就是两个 Horizen 外面包裹的 div 元素。
  2. 内部宽度没有进行相应的计算,始终为屏幕宽度。

现在就分别来解决这两个问题。

首先,新建 Singers/style.js 并增加:

import styled from'styled-components';
import style from '../../assets/global-style';

export const NavContainer  = styled.div`
  box-sizing: border-box;
  position: fixed;
  top: 95px;
  width: 100%;
  padding: 5px;
  overflow: hidden;
`;

在 Singers/index.js 中使用:

import { NavContainer } from "./style";

//...
// 返回的 JSX
return (
  <NavContainer>
    <Horizen list={categoryTypes} title={"分类 (默认热门):"}></Horizen>
    <Horizen list={alphaTypes} title={"首字母:"}></Horizen>
  </NavContainer>
)
//...

接下来 ,我们进入 baseUI/horizen-item/index.js 中:

// 加入声明
const Category = useRef (null);

// 加入初始化内容宽度的逻辑
useEffect (() => {
  let categoryDOM = Category.current;
  let tagElems = categoryDOM.querySelectorAll ("span");
  let totalWidth = 0;
  Array.from (tagElems).forEach (ele => {
    totalWidth += ele.offsetWidth;
  });
  categoryDOM.style.width = `${totalWidth}px`;
}, []);

// JSX 在Scroll下面,对第一个 div 赋予 ref
<Scroll direction={"horizental"}>
  <div ref={Category}>

# 点击 item 样式改变

现在整个列表就可以滑动啦。不过还有一个问题,当我们点击某个 item 的时候,应该呈现选中样式,然后并没有,因为我们并没有在点击的时候改变 oldVal 的值。

现在进入到 Singers/index.js 中,我们加入部分逻辑后代码如下:

import React, {useState} from 'react';
import Horizen from '../../baseUI/horizen-item';
import { categoryTypes, alphaTypes } from '../../api/config';
import { NavContainer } from "./style";

function Singers () {
  let [category, setCategory] = useState ('');
  let [alpha, setAlpha] = useState ('');

  let handleUpdateAlpha = (val) => {
    setAlpha (val);
  }

  let handleUpdateCatetory = (val) => {
    setCategory (val);
  }

  return (
    <NavContainer>
      <Horizen
        list={categoryTypes}
        title={"分类 (默认热门):"}
        handleClick={handleUpdateCatetory}
        oldVal={category}></Horizen>
      <Horizen
        list={alphaTypes}
        title={"首字母:"}
        handleClick={val => handleUpdateAlpha (val)}
        oldVal={alpha}></Horizen>
    </NavContainer>
  )
}

export default React.memo (Singers);

好,现在就有了我们开头的效果。现在你可以为所欲为地滑动、点击,都没有任何问题啦。

阅读全文