本节代码对应 GitHub 分支: chapter10
# 路由相关
首先构建路由。
//routes/index.js
import Search from '../application/Search';
export default [
{
path: "/",
component: Home,
routes: [
//...
{
path: "/search",
exact: true,
key: "search",
component: Search
}
]
]
现在在 application/Search 目录下新建 index.js:
import React, {useState, useEffect} from 'react';
import { CSSTransition } from 'react-transition-group';
import { Container } from './style';
function Search (props) {
// 控制动画
const [show, setShow] = useState (false);
useEffect (() => {
setShow (true);
}, []);
return (
<CSSTransition
in={show}
timeout={300}
appear={true}
classNames="fly"
unmountOnExit
onExited={() => props.history.goBack ()}
>
<Container>
<div onClick={() => (setShowfalse)}> 返回 </div>
</Container>
</CSSTransition>
)
}
export default Search;
相应的 style.js 中,我们来完成 Container 组件:
import styled from'styled-components';
import style from '../../assets/global-style';
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;
`
当然,为了给 Search 页面进出场的过渡效果,我们加上相关的动画钩子类的编写:
&.fly-enter, &.fly-appear {
transform: translate3d (100%, 0, 0);
}
&.fly-enter-active, &.fly-appear-active {
transition: all .3s;
transform: translate3d (0, 0, 0);
}
&.fly-exit {
transform: translate3d (0, 0, 0);
}
&.fly-exit-active {
transition: all .3s;
transform: translate3d (100%, 0, 0);
}
现在,我们进入 Home 组件,也就是跳转路由的地方,给 Search 组件一个入口。
//application/Home/index.js
<span className="iconfont search" onClick={() => props.history.push ('/search')}></span>
现在你点击搜索图标就能进入到 Search 页面,并且进出场都是会带滑动的过渡效果。
好,基础框架搭建就到这里,接下来,我们实现 Search 的具体内容。
# 搜索框基础组件开发
搜索框对于这个模块来说是一个非常关键的子组件,涉及到比较复杂的交互,可以说是这个模块的 "中枢" 部分。
在 baseUI/search-box 目录下,新建 index.js:
import React, {useRef, useState, useEffect, useMemo} from 'react';
import styled from'styled-components';
import style from '../../assets/global-style';
import { debounce } from './../../api/utils';
const SearchBox = (props) => {
const queryRef = useRef ();
const [query, setQuery] = useState ('');
// 从父组件热门搜索中拿到的新关键词
const { newQuery } = props;
// 父组件针对搜索关键字发请求相关的处理
const { handleQuery } = props;
// 根据关键字是否存在决定清空按钮的显示 / 隐藏
const displayStyle = query ? {display: 'block'}: {display: 'none'};
const handleChange = () => {
// 搜索框内容改变时的逻辑
};
const clearQuery = () => {
// 清空框内容的逻辑
}
return (
<SearchBoxWrapper>
<i className="iconfont icon-back" onClick={() => props.back ()}></i>
<input ref={queryRef} className="box" placeholder="搜索歌曲、歌手、专辑" value={query} onChange={handleChange}/>
<i className="iconfont icon-delete" onClick={clearQuery} style={displayStyle}></i>
</SearchBoxWrapper>
)
};
下面是 SearchBoxWrapper 的样式部分:
const SearchBoxWrapper = styled.div`
display: flex;
align-items: center;
box-sizing: border-box;
width: 100%;
padding: 0 6px;
padding-right: 20px;
height: 40px;
background: ${style ["theme-color"]};
.icon-back {
font-size: 24px;
color: ${style ["font-color-light"]};
}
.box {
flex: 1;
margin: 0 5px;
line-height: 18px;
background: ${style ["theme-color"]};
color: ${style ["highlight-background-color"]};
font-size: ${style ["font-size-m"]};
outline: none;
border: none;
border-bottom: 1px solid ${style ["border-color"]};
&::placeholder {
color: ${style ["font-color-light"]};
}
}
.icon-delete {
font-size: 16px;
color: ${style ["background-color"]};
}
`
好,现在就让我们来梳理一下搜索框的核心逻辑:
- 进场时 input 框应该出现光标
- 内容改变时要执行父组件传来的回调
- 当父组件点击热门搜索中的关键词时,如果新关键词与现在的 query 不同,则修改 query 并执行回调
现在就让我们来一一实现:
进场出现光标:
useEffect (() => {
queryRef.current.focus ();
}, []);
query 改变时执行回调:
// 监听 input 框的内容
const handleChange = (e) => {
setQuery (e.currentTarget.value);
};
// 缓存方法
let handleQueryDebounce = useMemo (() => {
return debounce (handleQuery, 500);
}, [handleQuery]);
useEffect (() => {
// 注意防抖
handleQueryDebounce (query);
}, [query]);
父组件点击了热门搜索的关键字,newQuery 更新:
useEffect (() => {
if (newQuery !== query){
setQuery (newQuery);
}
}, [newQuery]);
还剩下清空的逻辑:
const clearQuery = () => {
setQuery ('');
queryRef.current.focus ();
}
目前为止,SearchBox 组件就搭建的差不多了,我们把它对接到 Search 组件中。
//Search/index.js
import SearchBox from './../../baseUI/search-box/index';
// 组件内部
const [query, setQuery] = useState ('');
// 由于是传给子组件的方法,尽量用 useCallback 包裹,以使得在依赖未改变,始终给子组件传递的是相同的引用
const searchBack = useCallback (() => {
setShow (false);
}, []);
const handleQuery = (q) => {
setQuery (q);
}
// Container 中删除原来的内容,换成下面的
<Container>
<div className="search_box_wrapper">
<SearchBox back={searchBack} newQuery={query} handleQuery={handleQuery}></SearchBox>
</div>
</Container>
现在打开搜索页面,就能顺利地看到搜索框啦!接下来我们就来开发具体的 Search 组件的逻辑了。
阅读全文