Carousel

在本节,我们会使用第三方库React-Slick来构建“旋转木马组件”,并通过Ajax来获取数据源。

Mock API 地址:

https://raw.githubusercontent.com/ThoughtWorksWuhanUI/react-zero-to-one/master/mock_data/experiences.json

使用React-Slick

首先,你需要用最简单的例子,让React-Slick可以正常的在页面显示 ./app/components/Carousel/index.jsx:

import React from 'react';
import Slider from 'react-slick';

import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'

class Carousel extends React.Component {
  render() {
    var settings = {
      dots: true,
      infinite: true,
      speed: 500,
      slidesToShow: 1,
      slidesToScroll: 1
    };
    return (
      <Slider {...settings}>
        <div><h3>1</h3></div>
        <div><h3>2</h3></div>
        <div><h3>3</h3></div>
        <div><h3>4</h3></div>
        <div><h3>5</h3></div>
        <div><h3>6</h3></div>
      </Slider>
    );
  }
}

export default Carousel;

PS:也许你会考虑像官方文档那样,在styles.scss文件引用slick.scss和slick-theme.scss,但由于node-sass对相对路径资源解析一直都存在问题,所以Webpack编译会失败。

@import "~slick-carousel/slick/slick.scss";
@import "~slick-carousel/slick/slick-theme.scss";

开始写代码

本次Carousel的分步任务有三点:1.Carousel中每一项的DOM结构和样式 2.复写React-Slick中箭头的样式 3.Ajax请求获取数据

Carousel单个Item

可以首先去完成Carousel中每个Item的样式,它的DOM结构比较简单:

<div className={cx('item')}>
  <img src={item.image} />
  <div className={cx('description')}>
    <span className={cx('price')}>${item.price}</span>
    {item.name}
  </div>
  <div className={cx('comments')}>
    <span>{this.renderRankingStar(item.ranking)}</span>
    <span className={cx('count')}>{item.commentsCount}则评价</span>
  </div>
</div>

这里定义了一个函数renderStar,要根据rankingCount具体的值,来绘制评价的星级。(知识点:关于React中的箭头函数

renderRankingStar = (ranking) => {
  let j = 1;
  const stars = [];
  for (; j <= ranking; j++) {
    stars.push(<i key={`ranting-${j}`} className={cx('star')} />);
  }
  return stars;
}

然后根据DOM完成样式(参考Airbnb):

.container {
    max-width: 1020px;
    margin: 50px auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-family: Circular, -apple-system, BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;

    .item {
        width: 190px;

        .description {
            color: #484848;
            font-weight: 300;
            margin: 0px;
            word-wrap: break-word;
            font-size: 15px;
            line-height: 18px;
            letter-spacing: 0.2px;
            max-height: 36px;
            overflow: hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;

            .price {
                font-weight: 700;
                color: #484848;
                margin: 0px;
                word-wrap: break-word;
                font-size: 15px;
                line-height: 18px;
                letter-spacing: 0.2px;
                padding-top: 0px;
                padding-bottom: 0px;
                padding-right: 1px;
                display: inline;
            }
        }

        .star {
            background: url("./star.svg") no-repeat center center;
            height: 12px;
            width: 12px;
            display: inline-block;
            margin-right: 2px;
        }

        .comments {
            margin-top: 5px;

            .count {
                font-weight: normal;
                color: #484848;
                font-size: 12px;
                letter-spacing: 0.4px;
            }
        }
    }
}

你在写的时候,可以先硬编码(hard coding)一些假数据,方便快速验证结果。

复写React-Slick的箭头样式

Airbnb的previewButton和nextButton与Slick的默认样式不一致,我们需要通过CSS来复写样式:

.container {
    :global {
        .slick-prev {
            &:before {
                content: " ";
                background: url("./preview-arrow.svg") no-repeat center center;
                height: 24px;
                width: 24px;
                display: inline-block;
                left: -6px;
                position: absolute;
            }
        }

        .slick-next {
            &:before {
                content: " ";
                background: url("./next-arrow.svg") no-repeat center center;
                height: 24px;
                width: 24px;
                display: inline-block;
                left: -8px;
                position: absolute;
            }
        }

        .slick-prev.slick-disabled:before, .slick-next.slick-disabled:before {
            background-image: none;
        }
    }
}

这里要注意的是,需要添加一个:global的标签,CSS Modules在处理类名的时候,就会将它排除。(知识点:CSS Modules的Global和Local)。

数据请求

首先,需要添加你喜欢的Promise库,我使用ThoughtWorks技术雷达推荐的axios

那么问题来了,在React中,应该在哪里编写获取数据的代码呢?(即什么时候去调用ajax)答案是:componentDidMount

componentDidMount() {
  axios.get(this.props.url)
    .then((response) => {
      this.setState({ items: response.data })
    })
    .catch(function (error) {
      console.log(error);
    });
}

完整代码如下:

import React from 'react';
import Slider from 'react-slick';
import classNames from 'classnames/bind';
import axios from 'axios';
import styles from './styles.scss';

import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'

const cx = classNames.bind(styles);

class Carousel extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      items: []
    };
  }

  componentDidMount() {
    axios.get(this.props.url)
      .then((response) => {
        this.setState({ items: response.data })
      })
      .catch(function (error) {
        console.log(error);
      });
  }

  renderRankingStar = (ranking) => {
    let j = 1;
    const stars = [];
    for (; j <= ranking; j++) {
      stars.push(<i key={`ranting-${j}`} className={cx('star')} />);
    }
    return stars;
  }

  renderCarousel = (settings, items) => {
    if (items.length === 0) return null;
    return (
      <Slider {...settings}>
        {
          items.map((item, index) => (
            <div key={`carousel-item-${item.name}-${index}`}>
              <div className={cx('item')}>
                <img src={item.image} />
                <div className={cx('description')}>
                  <span className={cx('price')}>${item.price}</span>
                  {item.name}
                </div>
                <div className={cx('comments')}>
                  <span>{this.renderRankingStar(item.ranking)}</span>
                  <span className={cx('count')}>{item.commentsCount}则评价</span>
                </div>
              </div>
            </div>
          ))
        }
      </Slider>
    );
  }

  render() {
    var settings = {
      infinite: false,
      speed: 500,
      slidesToShow: 5,
      slidesToScroll: 1,
      draggable: false
    };
    return (
      <div className={cx('container', this.props.className)}>
        <h3>{this.props.title}</h3>
        {this.renderCarousel(settings, this.state.items)}
      </div>
    );
  }
};

Carousel.propTypes = {
  title: React.PropTypes.string
}

export default Carousel;
.container {
    max-width: 1020px;
    margin: 50px auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-family: Circular, -apple-system, BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;

    h3 {
        font-size: 28px;
        line-height: 36px;
        letter-spacing: -0.6px;
        padding-top: 2px;
        padding-bottom: 2px;
        color: #484848;
        font-weight: 700;
    }

    .item {
        width: 190px;

        img {
            max-width: 100%;
            max-height: 100%;
        }

        .description {
            color: #484848;
            font-weight: 300;
            margin: 0px;
            word-wrap: break-word;
            font-size: 15px;
            line-height: 18px;
            letter-spacing: 0.2px;
            max-height: 36px;
            overflow: hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;

            .price {
                font-weight: 700;
                color: #484848;
                margin: 0px;
                word-wrap: break-word;
                font-size: 15px;
                line-height: 18px;
                letter-spacing: 0.2px;
                padding-top: 0px;
                padding-bottom: 0px;
                padding-right: 1px;
                display: inline;
            }
        }

        .star {
            background: url("./star.svg") no-repeat center center;
            height: 12px;
            width: 12px;
            display: inline-block;
            margin-right: 2px;
        }

        .comments {
            margin-top: 5px;

            .count {
                font-weight: normal;
                color: #484848;
                font-size: 12px;
                letter-spacing: 0.4px;
            }
        }
    }

    :global {
        .slick-prev {
            &:before {
                content: " ";
                background: url("./preview-arrow.svg") no-repeat center center;
                height: 24px;
                width: 24px;
                display: inline-block;
                left: -6px;
                position: absolute;
            }
        }

        .slick-next {
            &:before {
                content: " ";
                background: url("./next-arrow.svg") no-repeat center center;
                height: 24px;
                width: 24px;
                display: inline-block;
                left: -8px;
                position: absolute;
            }
        }

        .slick-prev.slick-disabled:before, .slick-next.slick-disabled:before {
            background-image: none;
        }
    }
}

代码

你可以在prepare-env分支查看到当前步骤所有的代码

git clone [email protected]:ThoughtWorksWuhanUI/react-zero-to-one.git
git checkout carousel

本页编辑:Benwei

results matching ""

    No results matching ""