热门搜索样式布局
那么我们就需要在style.js中创建以下组件:
组件名 | 类型 | 含义 | 备注
—|—|—
SearchInfo | div | 整个外框 | 绝对定位
SearchInfoTitle | div | “热门搜索” |
SearchInfoSwith | span | “换一批” | 右浮动
SearchInfoList | div | 所有标签的外框 | overflow:hidden
(BFC、解决浮动元素的父元素高度塌陷)
SearchInfoItem | a | 各个标签 | display:block,左浮动
热门搜索框SearchInfo
1.header-style.js定义组件SearchInfo(绝对定位):
1 2 3 4 5 6 7 8 9
| export const SearchInfo = styled.div` position: absolute; left: 0; top: 56px; width: 240px; padding:0 20px; height:100px; box-shadow:0 0 8px rgba(0, 0, 0, .2); `;
|
2.header-index.js引入、使用组件SearchInfo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <SearchWrapper> <CSSTransition in={props.focused} timeout={200} classNames="slide" > <NavSearch className={props.focused ? "focused" : ""} onFocus={props.handleInputFocus} onBlur={props.handleInputBlur} /> </CSSTransition> <span className={props.focused ? "focused iconfont" : "iconfont"}>  </span> <SearchInfo> </SearchInfo> </SearchWrapper>
|
“热门搜索”SearchInfoTitle
1.header-style.js定义组件 SearchInfoTitle:
1 2 3 4 5 6 7
| export const SearchInfoTitle = styled.div` margin-top: 20px; margin-bottom: 15px; line-height: 20px; font-size:14px; color: #969696; `;
|
2.header-index.js引入、使用组件 SearchInfoTitle:
1 2 3
| <SearchInfo> <SearchInfoTitle>热门搜索</SearchInfoTitle> </SearchInfo>
|
“换一批”SearchInfoSwitch
1.header-style.js定义组件 SearchInfoSwitch(右浮动):
1 2 3 4
| export const SearchInfoSwitch = styled.div` float:right; font-size:13px; `;
|
2.header-index.js引入、使用组件 SearchInfoSwitch:
1 2 3 4 5 6
| <SearchInfo> <SearchInfoTitle> 热门搜索 <SearchInfoSwitch>换一批</SearchInfoSwitch> </SearchInfoTitle> </SearchInfo>
|
标签 SearchInfoItem
1.header-style.js定义组件 SearchInfoItem(左浮动,块级以定义宽高):
1 2 3 4 5 6 7 8 9 10 11 12
| export const SearchInfoItem = styled.a` display: block; float: left; line-height: 20px; padding: 0 5px; margin-right: 10px; margin-bottom: 10px; font-size: 12px; border: 1px solid color: border-radius: 3px; `;
|
2.header-index.js引入、使用组件 SearchInfoItem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <SearchInfo> <SearchInfoTitle> 热门搜索 <SearchInfoSwitch>换一批</SearchInfoSwitch> </SearchInfoTitle> <SearchInfoItem>理财</SearchInfoItem> <SearchInfoItem>毕业</SearchInfoItem> <SearchInfoItem>手帐</SearchInfoItem> <SearchInfoItem>简书交友</SearchInfoItem> <SearchInfoItem>spring</SearchInfoItem> <SearchInfoItem>古风</SearchInfoItem> <SearchInfoItem>故事</SearchInfoItem> <SearchInfoItem>暖寄归人</SearchInfoItem> </SearchInfo>
|
可以看到此时标签没有撑起框,因为之前我们把外边框高度写死了
SearchInfoList 所有标签的外框
1.header-style.js中,删除SearchInfo中的死高度100px,定义组件 SearchInfoList:
1 2 3
| export const SearchItemList = styled.div` overflow: hidden; `;
|
2.header-index.js引入、使用组件 SearchInfoList:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <SearchInfo> <SearchInfoTitle> 热门搜索 <SearchInfoSwitch>换一批</SearchInfoSwitch> </SearchInfoTitle> <SearchItemList> <SearchInfoItem>理财</SearchInfoItem> <SearchInfoItem>毕业</SearchInfoItem> <SearchInfoItem>手帐</SearchInfoItem> <SearchInfoItem>简书交友</SearchInfoItem> <SearchInfoItem>spring</SearchInfoItem> <SearchInfoItem>古风</SearchInfoItem> <SearchInfoItem>故事</SearchInfoItem> <SearchInfoItem>暖寄归人</SearchInfoItem> </SearchItemList> </SearchInfo>
|
实现点击显示效果
- 实现效果:点击搜索框则显示“热门搜索框”,搜索框失去焦点时“热门搜索框”消失。
- 实现思路:
- 新建一个函数
getListArea()
,该函数接收一个参数show
,show
为true时显示“热门搜索框”,为false时隐藏“热门搜索框”。
show
为true时,返回“热门搜索框”的所有组件。
show
为false时,返回null(空对象)。
- **在SearchWrapper组件中调用函数
getListArea()
,参数为props.focused
**。
1.header-index.js新建函数getListArea()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const getListArea = (show) => { if (show) { return ( <SearchInfo> <SearchInfoTitle> 热门搜索 <SearchInfoSwitch>换一批</SearchInfoSwitch> </SearchInfoTitle> <SearchItemList> <SearchInfoItem>理财</SearchInfoItem> <SearchInfoItem>毕业</SearchInfoItem> <SearchInfoItem>手帐</SearchInfoItem> <SearchInfoItem>简书交友</SearchInfoItem> <SearchInfoItem>spring</SearchInfoItem> <SearchInfoItem>古风</SearchInfoItem> <SearchInfoItem>故事</SearchInfoItem> <SearchInfoItem>暖寄归人</SearchInfoItem> </SearchItemList> </SearchInfo> ) } else { return null; } }
|
2.header-index.js调用函数getListArea()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <SearchWrapper> <CSSTransition in={props.focused} timeout={200} classNames="slide" > <NavSearch className={props.focused ? "focused" : ""} onFocus={props.handleInputFocus} onBlur={props.handleInputBlur} /> </CSSTransition> <span className={props.focused ? "focused iconfont" : "iconfont"}>  </span> {getListArea(props.focused)} </SearchWrapper>
|
Ajax获取推荐数据
上面我们显示的标签是写死的,现在我们要实现点击搜索框以后 通过AJAX获取数据:
思路:
- 往组件的state中增添一个**
list
数组用于存储标签数据**
- 使用redux-thunk中间件使action可以是函数
- 中间件指的是action和store的“中间”,实际是对dispatch的一个升级
- 原本actionCreators中返回的action都只能是对象,使用redux-thunk后可返回函数(该函数接受dispatch方法作为参数)
- 在创建store时添加中间件(dispatch是store的方法)
- public文件夹下新建api文件夹,新建json文件,模拟假数据
- 注意:当js页面中向某个接口发出请求获取数据时,项目会先去node中找,找不到就会去public文件夹中找相应的路径下是否有数据。
- 所以假设接口是
/api/headerList.json
,那可以在public文件夹下=>新建api文件夹=>新建headerList.json,放置相应假数据
- 将AJAX请求写在函数action中,异步AJAX请求需要借助第三方模块axios
- reducer处理action,将AJAX获取到的数据放到state的list中
- 组件使用connect的参数1获取state的list,使用map()循环渲染list数组于页面上。
目前header-index.js中Header组件是无状态组件,只能通过函数嵌套来添加函数,但接下来会添加比较多东西,所以先将Header组件转回 容器组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| class Header extends Component { getListArea(show) { if (show) { return ( <SearchInfo> <SearchInfoTitle> 热门搜索 <SearchInfoSwitch>换一批</SearchInfoSwitch> </SearchInfoTitle> <SearchItemList> <SearchInfoItem>理财</SearchInfoItem> <SearchInfoItem>毕业</SearchInfoItem> <SearchInfoItem>手帐</SearchInfoItem> <SearchInfoItem>简书交友</SearchInfoItem> <SearchInfoItem>spring</SearchInfoItem> <SearchInfoItem>古风</SearchInfoItem> <SearchInfoItem>故事</SearchInfoItem> <SearchInfoItem>暖寄归人</SearchInfoItem> </SearchItemList> </SearchInfo> ) } else { return null; } }
render() { return ( <HeaderWrapper> <Logo /> <Nav> <NavItem className="left active">首页</NavItem> <NavItem className="left">下载App</NavItem> <NavItem className="right">登录</NavItem> <NavItem className="right"> <span className="iconfont"></span> </NavItem> <SearchWrapper> <CSSTransition in={this.props.focused} timeout={200} classNames="slide" > <NavSearch className={this.props.focused ? "focused" : ""} onFocus={this.props.handleInputFocus} onBlur={this.props.handleInputBlur} /> </CSSTransition> <span className={this.props.focused ? "focused iconfont" : "iconfont"}>  </span> {this.getListArea(this.props.focused)} </SearchWrapper> </Nav> <Addition> <Button className="writting"> <span className="iconfont"></span> 写文章 </Button> <Button className="reg">注册</Button> </Addition> </HeaderWrapper> ); } }
|
- 使用class和Component,将
props
转回this.props
(可使用ctrl+d多选辅助)
- 将函数getListArea放入容器组件中,使用
this.getListArea()
进行调用
(注意:写在组件中的函数是getListArea(){...}
,写在组件外的函数是const getListArea = ()=>{...}
)
state中增添一个list
数组
headet-store-reducer.js往state中增添一个list
数组用于存储标签数据:
1 2 3 4
| const defaultState = fromJS({ focused: false, list: [] });
|
配置redux-thunk中间件
使用redux-thunk使action中可以写函数,我们将AJAX请求写在函数action中。
1.安装redux-thunk:
复习中间件:指的是action和store的“中间”,实际是对dispatch的一个升级。
2.创建store时(即src-store-index.js)添加中间件:
1 2 3 4 5 6 7 8 9 10
| import { createStore, compose, applyMiddleware } from "redux"; import reducer from "./reducer"; import thunk from "redux-thunk"
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore(reducer, composeEnhancers( applyMiddleware(thunk) ));
export default store;
|
通过redux提供的applyMiddleware可将redux-thunk中引入的thunk添加进store。
public下模拟假数据
- AJAX请求的接口下得返回数据给页面,那么返回的数据就需要我们进行模拟。
- 注意:当js页面中向某个接口发出请求获取数据时,项目会先去node中找,找不到就会去public文件夹中找相应的路径下是否有数据。
- 所以假设接口是
/api/headerList.json
,那可以在public文件夹下=>新建api文件夹=>新建headerList.json,放置相应假数据
- 注意:这种写法就不能使用mock.js去模拟假数据,因为public下的文件中获取到的内容是被直接显示的,不会进行编译。而且mockjs会帮助拦截请求,写到public下也没办法让使用AJAX的组件引入mockjs。
public文件夹下=>新建api文件夹=>新建headerList.json:
1 2 3 4
| { "sucess":true, "data":["简书","理财","毕业","手帐","简书交友","spring","古风","故事","暖寄归人"] }
|
组件中发送含有AJAX的action
当搜索框NavSearch聚焦时会触动handleInputFocus函数,所以我们将AJAX获取数据的action(即actionCreators.js中的getList())写在handleInputFocus函数中即可:
1 2 3 4 5 6 7 8 9 10 11
| const mapDispatchProps = (dispatch) => { return { handleInputFocus() { dispatch(actionCreators.getList()); dispatch(actionCreators.searchFocus()); }, handleInputBlur() { dispatch(actionCreators.searchBlur()); } } }
|
action中使用axios库进行AJAX请求
1.安装axios库:
2.header-store-actionCreators.js中,引入axios并创建getList()用于获取AJAX数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const changeList = (data) => ({ type: constants.CHANG_LIST, data });
export const getList = () => { return (dispatch) => { axios.get("/api/headerList.json").then((res) => { const data = res.data; dispatch(changeList(data.data)); }).catch(() => { console.log("error"); }); }; };
|
原本actionCreators中返回的action都只能是对象,使用redux-thunk后可返回函数(该函数接受dispatch方法作为参数)
3.注意:fromJS会自动将对象中的数组变成immutable类型的数组,所以在reducer中使用set()修改数组的时候要求修改的数据也是immutable类型的,否则数据类型就乱了。所以我们要在actionCreators.js中使用fromJS使data也成为immutable类型
1
| import { fromJS } from "immutable";
|
1 2 3 4
| export const changeList = (data) => ({ type: constants.CHANG_LIST, data: fromJS(data) });
|
reducer处理action中AJAX获取到的数据
action发出以后就要到reducer.js中处理这个action,将AJAX获取到的数据放到list中:
1 2 3
| if (action.type === constants.CHANG_LIST) { return state.set("list", action.data); }
|
获取list数据循环显示在页面上
原本我们把标签数据都写死了,现在我们要在connect方法的参数1 mapStateToProps函数中将store的list取到,然后修改getListArea方法,循环显示list中的数据。
header-index.js:
1.使用mapStateToProps函数将store的list取到:
1 2 3 4 5 6
| const mapStateToProps = (state) => { return { focused: state.getIn(["header", "focused"]), list: state.getIn(["header", "list"]) } }
|
2.修改getListArea方法,循环显示list中的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| getListArea() { if (this.props.focused) { return ( <SearchInfo> <SearchInfoTitle> 热门搜索 <SearchInfoSwitch>换一批</SearchInfoSwitch> </SearchInfoTitle> <SearchItemList> { this.props.list.map((item) => { return <SearchInfoItem key={item}>{item}</SearchInfoItem> }) } </SearchItemList> </SearchInfo> ) } else { return null; } }
|
代码优化
actionCreator.js需要导出的函数放一起
- actionCreator中作为AJAX请求的函数changeList是不需要导出的,建议将不需要导出的函数统一放在顶部/底部。
- 明确表示哪些函数是自己用的,哪些函数是导出给别人用的。
actionCreator.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { constants } from "../store"; import axios from "axios"; import { fromJS } from "immutable";
const changeList = (data) => ({ type: constants.CHANG_LIST, data: fromJS(data) });
export const searchFocus = () => ({ type: constants.SEARCH_FOCUS });
export const searchBlur = () => ({ type: constants.SEARCH_BLUR });
export const getList = () => { return (dispatch) => { axios.get("/api/headerList.json").then((res) => { const data = res.data; dispatch(changeList(data.data)); }).catch(() => { console.log("error"); }); }; };
|
函数顶部解构赋值一次性获取props上的值
组件中很多地方会重复使用this.props.xxx
来取值,那么我们可以在函数顶部使用const {xx,xxx,xxx} = this.props;
来一次性获取props上的值,接下来只要使用xx
即可。
header-index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| import React, { Component } from 'react' import { connect } from "react-redux"; import { actionCreators } from "./store"; import { CSSTransition } from 'react-transition-group'; import { HeaderWrapper, Logo, Nav, NavItem, NavSearch, Addition, Button, SearchWrapper, SearchInfo, SearchInfoTitle, SearchInfoSwitch, SearchInfoItem, SearchItemList, } from "./style";
class Header extends Component { getListArea() { const { focused, list } = this.props; if (focused) { return ( <SearchInfo> <SearchInfoTitle> 热门搜索 <SearchInfoSwitch>换一批</SearchInfoSwitch> </SearchInfoTitle> <SearchItemList> { list.map((item) => { return <SearchInfoItem key={item}>{item}</SearchInfoItem> }) } </SearchItemList> </SearchInfo> ) } else { return null; } }
render() { const { focused, handleInputFocus, handleInputBlur } = this.props; return ( <HeaderWrapper> <Logo /> <Nav> <NavItem className="left active">首页</NavItem> <NavItem className="left">下载App</NavItem> <NavItem className="right">登录</NavItem> <NavItem className="right"> <span className="iconfont"></span> </NavItem> <SearchWrapper> <CSSTransition in={focused} timeout={200} classNames="slide" > <NavSearch className={focused ? "focused" : ""} onFocus={handleInputFocus} onBlur={handleInputBlur} /> </CSSTransition> <span className={focused ? "focused iconfont" : "iconfont"}>  </span> {this.getListArea()} </SearchWrapper> </Nav> <Addition> <Button className="writting"> <span className="iconfont"></span> 写文章 </Button> <Button className="reg">注册</Button> </Addition> </HeaderWrapper> ); } }
const mapStateToProps = (state) => { return { focused: state.getIn(["header", "focused"]), list: state.getIn(["header", "list"]) } }
const mapDispatchProps = (dispatch) => { return { handleInputFocus() { dispatch(actionCreators.getList()); dispatch(actionCreators.searchFocus()); }, handleInputBlur() { dispatch(actionCreators.searchBlur()); } } }
export default connect(mapStateToProps, mapDispatchProps)(Header);
|
用switch替换大量if
- 在reducer中我们使用了大量的if语句,使用switch替换会更好。
- 一般要使用 break 来阻止代码自动地向下一个 case 运行。但由于我们使用了return,也就不需要break了。
header-store-reducer.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import * as constants from "./constants"; import { fromJS } from "immutable";
// immutable对象 是不可改变的 const defaultState = fromJS({ focused: false, list: [] });
export default (state = defaultState, action) => { switch (action.type) { case constants.SEARCH_FOCUS: return state.set("focused", true); case constants.SEARCH_BLUR: return state.set("focused", false); case constants.CHANG_LIST: return state.set("list", action.data); default: return state; } }
|