本节代码对应 GitHub 分支: chapter6

仓库传送门 (opens new window)

# 封装 UI 代码

现在 Album 里面的 JSX 过于庞大,影响可读性,可以做一下封装。

将复杂渲染的代码拆解如下:

const renderTopDesc = () => {
  return (
    <TopDesc background={currentAlbum.coverImgUrl}>
      <div className="background">
        <div className="filter"></div>
      </div>
      <div className="img_wrapper">
        <div className="decorate"></div>
        <img src={currentAlbum.coverImgUrl} alt="" />
        <div className="play_count">
          <i className="iconfont play">&#xe885;</i>
          <span className="count">{getCount (currentAlbum.subscribedCount)}</span>
        </div>
      </div>
      <div className="desc_wrapper">
        <div className="title">{currentAlbum.name}</div>
        <div className="person">
          <div className="avatar">
            <img src={currentAlbum.creator.avatarUrl} alt="" />
          </div>
          <div className="name">{currentAlbum.creator.nickname}</div>
        </div>
      </div>
    </TopDesc>
  )
}

const renderMenu = () => {
  return (
    <Menu>
      <div>
        <i className="iconfont">&#xe6ad;</i>
        评论
      </div>
      <div>
        <i className="iconfont">&#xe86f;</i>
        点赞
      </div>
      <div>
        <i className="iconfont">&#xe62d;</i>
        收藏
      </div>
      <div>
        <i className="iconfont">&#xe606;</i>
        更多
      </div>
    </Menu>
  )
};

const renderSongList = () => {
  return (
    <SongList>
      <div className="first_line">
        <div className="play_all">
          <i className="iconfont">&#xe6e3;</i>
          <span > 播放全部 <span className="sum">(共 {currentAlbum.tracks.length} 首)</span></span>
        </div>
        <div className="add_list">
          <i className="iconfont">&#xe62d;</i>
          <span > 收藏 ({getCount (currentAlbum.subscribedCount)})</span>
        </div>
      </div>
      <SongItem>
        {
          currentAlbum.tracks.map ((item, index) => {
            return (
              <li key={index}>
                <span className="index">{index + 1}</span>
                <div className="info">
                  <span>{item.name}</span>
                  <span>
                    {getName (item.ar)} - {item.al.name}
                  </span>
                </div>
              </li>
            )
          })
        }
      </SongItem>
    </SongList>
  )
}

return (
  <CSSTransition
    in={showStatus}
    timeout={300}
    classNames="fly"
    appear={true}
    unmountOnExit
    onExited={props.history.goBack}
  >
    <Container>
      <Header ref={headerEl} title={title} handleClick={handleBack} isMarquee={isMarquee}></Header>
      {!isEmptyObject (currentAlbum) ?
        (
          <Scroll
            bounceTop={false}
            onScroll={handleScroll}
          >
            <div>
              { renderTopDesc () }
              { renderMenu () }
              { renderSongList () }
            </div>
          </Scroll>
        )
        : null
      }
      { enterLoading ? <Loading></Loading> : null}
    </Container>
  </CSSTransition>
)

这样整个返回的 JSX 代码就清爽了不少,给人一目了然的感觉。

# useCallback 优化 function props

将传给子组件的函数用 useCallback 包裹,这也是 useCallback 的常用场景。

const handleBack = useCallback (() => {
  setShowStatus (false);
}, []);

const handleScroll = useCallback ((pos) => {
  //xxx
}, [currentAlbum]);

以此为例,如果不用 useCallback 包裹,父组件每次执行时会生成不一样的 handleBack 和 handleScroll 函数引用,那么子组件每一次 memo 的结果都会不一样,导致不必要的重新渲染,也就浪费了 memo 的价值。

因此 useCallback 能够帮我们在依赖不变的情况保持一样的函数引用,最大程度地节约浏览器渲染性能。

OK,歌单详情模块现在开发基本完成。

阅读全文