7-1项目目录搭建Styled-Componen与Reset.css的结合使用
7-2使用styled-components完成hedaers组件布局
choome/hedar/index.js
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
7-4使用inconfont嵌入头部的图标
7-5搜索框动画效果实现
class Header extends Component {
constructor(props){
super(props);
this.state={
focused:false;
}}
className=""
&.focused{
width:180px;
<NavSearch
handleInputFocus(){
this.setState({
focus:true}}
onBlur={this.handleInputFocus}
react-transition-group
import{CssTransition}
classNames="slide">
slide-enter
slide-enter-active
slide-exit
.slide-exit-active{
width:160px;}
export const NavSearch = styled.input.attrs({
placeholder: '搜索'
})`
width: 160px;
height: 38px;
padding: 0 30px 0 20px;
margin-top: 9px;
margin-left: 20px;
box-sizing: border-box;
border: none;
outline: none;
border-radius: 19px;
background: #eee;
font-size: 14px;
color: #666;
&::placeholder {
color: #999;
}
&.focused {
width: 240px;
}
&.slide-enter {
transition: all .2s ease-out;
}
&.slide-enter-active {
width: 240px;
}
&.slide-exit {
transition: all .2s ease-out;
}
&.slide-exit-active {
width: 160px;
}
`;
<SearchWrapper>
<CSSTransition
in={focused}
timeout={200}
classNames="slide"
>
<NavSearch
className={focused ? 'focused': ''}
onFocus={() => handleInputFocus(list)}
onBlur={handleInputBlur}
></NavSearch>
</CSSTransition>
<i className={focused ? 'focused iconfont zoom': 'iconfont zoom'}>

</i>
{this.getListArea()}
handleInputFocus(list) {
(list.size === 0) && dispatch(actionCreators.getList());
dispatch(actionCreators.searchFocus());
},
handleInputBlur() {
dispatch(actionCreators.searchBlur());
},
store/index.js
const store=createSTore();
import reducer
const defaultState={};
export default(state=defaultState,active)=>{
return state;}
import reducer from'./reducer';
provider store={store}
const mapStateToProps=(state)=>{
return {
}}
const mapDispatchToProps=(dispatch)=>.{
return {}}
const mapDispatchToProps=(dispatch)=>
export default connect(mapStateToProps,mapDispatch
composed
7-6使用react-redux进行应用数据管理
connect方法header和store建立连接
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { CSSTransition } from 'react-transition-group';
import { actionCreators } from './store';
import { actionCreators as loginActionCreators } from '../../pages/login/store'
import {
HeaderWrapper,
Logo,
Nav,
NavItem,
SearchWrapper,
NavSearch,
SearchInfo,
SearchInfoTitle,
SearchInfoSwitch,
SearchInfoList,
SearchInfoItem,
Addition,
Button
} from './style';
class Header extends Component {
getListArea() {
const { focused, list, page, totalPage, mouseIn, handleMouseEnter, handleMouseLeave, handleChangePage } = this.props;
const newList = list.toJS();
const pageList = [];
if (newList.length) {
for (let i = (page - 1) * 10; i < page * 10; i++) {
pageList.push(
<SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
)
}
}
if (focused || mouseIn) {
return (
<SearchInfo
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch
onClick={() => handleChangePage(page, totalPage, this.spinIcon)}
>
<i ref={(icon) => {this.spinIcon = icon}} className="iconfont spin"></i>
换一批
</SearchInfoSwitch>
</SearchInfoTitle>
<SearchInfoList>
{pageList}
</SearchInfoList>
</SearchInfo>
)
}else {
return null;
}
}
render() {
const { focused, handleInputFocus, handleInputBlur, list, login, logout } = this.props;
return (
<HeaderWrapper>
<Link to='/'>
<Logo/>
</Link>
<Nav>
<NavItem className='left active'>首页</NavItem>
<NavItem className='left'>下载App</NavItem>
{
login ?
<NavItem onClick={logout} className='right'>退出</NavItem> :
<Link to='/login'><NavItem className='right'>登陆</NavItem></Link>
}
<NavItem className='right'>
<i className="iconfont"></i>
</NavItem>
<SearchWrapper>
<CSSTransition
in={focused}
timeout={200}
classNames="slide"
>
<NavSearch
className={focused ? 'focused': ''}
onFocus={() => handleInputFocus(list)}
onBlur={handleInputBlur}
></NavSearch>
</CSSTransition>
<i className={focused ? 'focused iconfont zoom': 'iconfont zoom'}>

</i>
{this.getListArea()}
</SearchWrapper>
</Nav>
<Addition>
<Link to='/write'>
<Button className='writting'>
<i className="iconfont"></i>
写文章
</Button>
</Link>
<Button className='reg'>注册</Button>
</Addition>
</HeaderWrapper>
);
}
}
const mapStateToProps = (state) => {
return {
focused: state.getIn(['header', 'focused']),
list: state.getIn(['header', 'list']),
page: state.getIn(['header', 'page']),
totalPage: state.getIn(['header', 'totalPage']),
mouseIn: state.getIn(['header', 'mouseIn']),
login: state.getIn(['login', 'login'])
}
}
const mapDispathToProps = (dispatch) => {
return {
handleInputFocus(list) {
(list.size === 0) && dispatch(actionCreators.getList());
dispatch(actionCreators.searchFocus());
},
handleInputBlur() {
dispatch(actionCreators.searchBlur());
},
handleMouseEnter() {
dispatch(actionCreators.mouseEnter());
},
handleMouseLeave() {
dispatch(actionCreators.mouseLeave());
},
handleChangePage(page, totalPage, spin) {
let originAngle = spin.style.transform.replace(/[^0-9]/ig, '');
if (originAngle) {
originAngle = parseInt(originAngle, 10);
}else {
originAngle = 0;
}
spin.style.transform = 'rotate(' + (originAngle + 360) + 'deg)';
if (page < totalPage) {
dispatch(actionCreators.changePage(page + 1));
}else {
dispatch(actionCreators.changePage(1));
}
},
logout() {
dispatch(loginActionCreators.logout())
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Header);
聚焦
handleInputFocus(list) {
(list.size === 0) && dispatch(actionCreators.getList());
dispatch(actionCreators.searchFocus());
},
reducer.js
import * as constants from './constants';
import { fromJS } from 'immutable';
const defaultState = fromJS({
focused: false,
mouseIn: false,
list: [],
page: 1,
totalPage: 1
});
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.CHANGE_LIST:
return state.merge({
list: action.data,
totalPage: action.totalPage
});
case constants.MOUSE_ENTER:
return state.set('mouseIn', true);
case constants.MOUSE_LEAVE:
return state.set('mouseIn', false);
case constants.CHANGE_PAGE:
return state.set('page', action.page);
default:
return state;
}
}
7-7使用combinReducers完成对数据的拆分管理
export default {
reducer存放很多数据会导致代码不可维护,拆分成多个reducer
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
reducer存放过多的数据会导致代码的不可维护,将reducer的数据放到不同的reducer当中,header相当的操作放到headerReducer当中去
import { combineReducers } from 'redux-immutable';
import { reducer as headerReducer } from '../common/header/store';
import { reducer as homeReducer } from '../pages/home/store';
import { reducer as detailReducer } from '../pages/detail/store';
import { reducer as loginReducer } from '../pages/login/store';
const reducer = combineReducers({
header: headerReducer,
home: homeReducer,
detail: detailReducer,
login: loginReducer
});
export default reducer;
store/actionCreators.
import * as constants from './constants';
import { fromJS } from 'immutable';
import axios from 'axios';
const changeList = (data) => ({
type: constants.CHANGE_LIST,
data: fromJS(data),
totalPage: Math.ceil(data.length / 10)
});
export const searchFocus = () => ({
type: constants.SEARCH_FOCUS
});
export const searchBlur = () => ({
type: constants.SEARCH_BLUR
});
export const mouseEnter = () => ({
type: constants.MOUSE_ENTER
});
export const mouseLeave = () => ({
type: constants.MOUSE_LEAVE
});
export const changePage = (page) => ({
type: constants.CHANGE_PAGE,
page
});
export const getList = () => {
return (dispatch) => {
axios.get('/api/headerList.json').then((res) => {
const data = res.data;
dispatch(changeList(data.data));
}).catch(() => {
console.log('error');
})
}
};
7-9使用immutable.js来管理store当中的数据
immutable.js
facebook,immutable对象,
7-10使用redux-immutable统一数据格式
yarn add redux-immutable
actionCreator.js
import * as
immutable对象的set方法会结合之前的immuatble对象的值和设置的值返回一个全新的对象
7-11热门搜索样式布局
<SearchInfoTitle>
热门搜索
<SearchInfoSwitch
onClick={() => handleChangePage(page, totalPage, this.spinIcon)}
>
<i ref={(icon) => {this.spinIcon = icon}} className="iconfont spin"></i>
换一批
</SearchInfoSwitch>
</SearchInfoTitle>
export const SearchInfo = styled.div`
position: absolute;
left: 0;
top: 56px;
width: 240px;
padding: 0 20px;
box-shadow: 0 0 8px rgba(0, 0, 0, .2);
background: #fff;
`;
export const SearchInfoTitle = styled.div`
margin-top: 20px;
margin-bottom: 15px;
line-height: 20px;
font-size: 14px;
color: #969696;
`;
export const SearchInfoSwitch = styled.span`
float: right;
font-size: 13px;
cursor: pointer;
.spin {
display: block;
float: left;
font-size: 12px;
margin-right: 2px;
transition: all .2s ease-in;
transform-origin: center center;
}
`;
export const SearchInfoList = styled.div`
overflow: hidden;
`;
export const SearchInfoItem = styled.a`
display: block;
float: left;
line-height: 20px;
padding: 0 5px;
margin-right: 10px;
margin-bottom: 15px;
font-size: 12px;
border: 1px solid #ddd;
color: #787878;
border-radius: 3px;
`;
7-12ajax获取推荐数据
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
));
export default store;
import { combineReducers } from 'redux-immutable';
import { reducer as headerReducer } from '../common/header/store';
import { reducer as homeReducer } from '../pages/home/store';
import { reducer as detailReducer } from '../pages/detail/store';
import { reducer as loginReducer } from '../pages/login/store';
const reducer = combineReducers({
header: headerReducer,
home: homeReducer,
detail: detailReducer,
login: loginReducer
});
export default reducer;
yarn add axios
import * as constants from './constants';
import { fromJS } from 'immutable';
import axios from 'axios';
const changeList = (data) => ({
type: constants.CHANGE_LIST,
data: fromJS(data),
totalPage: Math.ceil(data.length / 10)
});
export const searchFocus = () => ({
type: constants.SEARCH_FOCUS
});
export const searchBlur = () => ({
type: constants.SEARCH_BLUR
});
export const mouseEnter = () => ({
type: constants.MOUSE_ENTER
});
export const mouseLeave = () => ({
type: constants.MOUSE_LEAVE
});
export const changePage = (page) => ({
type: constants.CHANGE_PAGE,
page
});
export const getList = () => {
return (dispatch) => {
axios.get('/api/headerList.json').then((res) => {
const data = res.data;
dispatch(changeList(data.data));
}).catch(() => {
console.log('error');
})
}
};
headerList.json
{
"success": true,
"data": ["高考","区块链","三生三世","崔永元","vue","小程序","茶点微小说","萨沙讲史堂","夜幕下的地安门","擦亮你的眼","理财","毕业","手帐","简书交友","spring","古风","故事","暖寄归人","旅行","连载","教育","简书","生活","投稿","历史","PHP","考研","docker","EOS","微信小程序","PPT","职场","大数据","创业","书评","东凤","饱醉豚","雨落荒原","程序员","爬虫","时间管理","kotlin","数据分析","阴阳合同","设计","红楼梦","父亲节","女人和衣服","swift","高考作文"]
}