项目实战之简书Header组件

cd -跳转到目录下 是change directory缩写

&表示当前元素,例如
a{
&.b {}
}
编译之后就是
a.b{}
没有&就成了a .b(有个空格,b这个class成了a的后代元素的了)
app.test.js是自动化测试文件
引入css实际上是全局引用的

npm install --save styled-components第三方模块,对样式进行管理
并且自己的样式只对自己生效

然后就可以修改index.css文件 改为 style.js
引入是 import ‘./style.js’;

css中的body是全局的样式

import { injectGlobal } from 'styled-components';
injectGlobal`
	body.....
`

reset.css 放入injectGlobal里 可以保证所有浏览器上效果一样

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

src中创建一个common里创建header的文件夹,index.js

src下的index.js 中引入App组件

ReactDOM.render(<App />,document.getElementById('root'));
header/index.js
import { HeaderWrapper ,Logo,Nav,NavSearch,Addition,Button,SearchWrapper} from './style';
import { CSSTransition }from 'react-transition-group';
const Header =(props) =>{return和 里面的内容全部放到这里 中间的this.props替换为props}
//提升性能
class Header extends Component {
	constructior(props){
		super(props};
		this.state ={ focused:false;}
		this.handleInputFocus = this.handleInputFocus.bind(this);
		this.handleInputBlur = this.handleInputBlur.bind(this);
		}
	render(){
		return (
<HeaderWrapper>
	<Logo href='/' />
	<Nav>
		<NavItem className='left active'>首页</NavItem>
		<NavItem className='left'>下载App</NavItem>
		<NavItem className='right'>登陆</NavItem>
		<NavItem className='right'>Aa</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}//离开聚焦时
		</NavSearch>
		</CSSTransition>
		<i className={this.props.focusd?'focused iconfont':' '}> &#xe614 </i>
		</SearchWrapper>
	</Nav>
	<Addition>
		<Button className='reg'>注册</Button>
		<Button className='writting'> 写文章</Button>
	</Addition>
</HeaderWrapper>
)
}
handleInputFocus () {
	this.setState({
	focused:true})
}
handleInputFocus () {
	this.setState({
	focused:false})
}

App.js中
引入Header 和<Header />

在header文件夹下创建style.js

import styled from 'styled-components';
import logoPic from '../../statics/logo.png';
export const HeaderWrapper = styled.div`
 position:relative;
 height :56px;
 background:red;
`;
export const Logo = Styled.a.attrs({ href:'/' })`//跳转
	positon:absolute;
	top:0;
	left:0;
	display:block;
	width:100px;
	height:56px;
	background:url(${logoPic});
	background-size:contain;//规定背景图像的尺寸 contain	把图像图像扩展至最大尺寸,以使其宽度和高度完全适应内容区域。
`;
export const Nav =styled.div`
	width:960px;
	height:100%;
	padding-right:70px;
	box-sizing:border-box;//width不会被撑大
	margin: 0 auto ;
	`
export const NavItem = styled.div`
	line-height: 56px;
	padding: 0 15px;//上下  左右
	font-size:17px;
	color:#333;
	&.left { float:left;}
	&.right {float:right;  color:#969696;}
	&.active { color:#ea6f5a;}
`
export const NavSearch = styled.input.attrs({placeholder:'搜索‘})
`
width:160px;
height:38px;
border:none;
margin-top:9px;
margin-left:20px;
padding:0 30px 0 20px;
outline:none;//设置 4 个边框的样式 
border-radius:19px;
box-sizing:border-box;//防止由padding width作用下 作用框被撑开
background:#eee;
font-size:14px;
color:#666;
&::placeholder {color :#999;}
&.focused {
width:240px;
.iconfont {
	background:#777;
	color:#fff;
}
}
&.slide-enter{
	width:160px;
	transition: all .2s ease-out;//200=0.2s 缓慢结束
}
&.slide-enter-active {
	width:240px;
}
&.slide-exit{
	transition: all .2s ease-out;//200=0.2s 缓慢结束
}
&.slide-exit-active {
	width:160px;
}

`;

export const Addition =styled.div`
	position:absolute;
	right:0;
	top:0;
	height:56px;
`;
export const Button =styled.div`
	float:right;
	margin-top:9px;
	line-height:38px;//定义行高
	border-radius:19px;
	border:1px solid #ec6149;
	font-size:14px;
	margin-right:20px;
	padding:0 20px;
	&.reg{
	color:#ec6149;
	}
	&.writting { color:#fff;  background:#ec6149;}
`
export const SearchWrapper = styled.div`
float:left;

position:relative;
.iconfont {
	position :absolute;
	right:5px;
	bottom:5px;
	width:30px;
	line-height:30px;
	border-radius:15px;
	text-align:center;
	}
`

简书的logo放在src/statics文件夹中

在iconfont里找图标 下载到本地
html是使用说明
其中 eot svg ttf woff css格式是有用的文件

如何使用呢?在statics中建立新文件夹iconfont 把这些有用的文件拖进来
用编译器打开css文件 调整格式
我们只需要@font-face{…} 和 .iconfont{…}
url里的加上相对路径./(除了开头为data的
将这个css文件重命名成js
同样引入injectGlobal from styled-components
把内容全部加入到

injectGlobal` ....`

希望他全局生效,在src/index.js中引入该js文件

图标即为<i class="iconfont">&#x33;</i>
在demo-unicode.html中图标下有编码 复制替换掉之前那个33

浮动的东西不占位 若想要SearchWrapper实现一个装饰其中一小块,
必须也要浮动

padding: 上 右 下 左

要想实现动画效果实现,首先安装react-transition-group
然后用CSSTransition标签包裹起来,添加属性 in timeout classNames ,

ease 规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic-bezier(0.25,0.1,0.25,1))。
ease-in 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))。
ease-out 规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))。
ease-in-out 规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))。

为了今后维护方便,尽量把数据加到redux,类似this.state里边的focused

安装redux react-redux(方便在react中使用redux)
src下创建store 在里面创建index.js
在store下创建reducer.js

//store/index.js
import { createStore } from 'redux';
import  reducer  from './reducer';
const store = createStore(reducer);
export default store;

//store/reducer.js
const defaultState ={};
export default (state = defaultState , action )=> {
	return state;
}

reducer.js最后导出的是一个纯函数

纯函数:有固定的输入就有固定的输出且没有副作用
一个函数执行过程对产生了外部可观察的变化那么就说这个函数是有副作用的。

App.js中

import store from './store/';
import { Provider }from 'react-redux';
//这个Provider的使用只需在class APP下return里面作为标签包裹起来
....
return (
	<Provider store={store}>//provider标签作用:store里数据都提供给了provider里所有组件
		<Header />
	</ Provider >
);

//然后在header index.js再进行连接 connect

import { connect }from 'react-redux';


还需要修改export default 
改为 
export default connect (mapStateToProps,mapDispatchToProps)(Header);

const mapStateToProps =( state )=> {//store里数据state如何映射到props
	return {
	focused:state.focused}
	//然后把this.state.focused全部替换为this.props...
}

const mapDispatchToProps =( dispatch )=> {//组件改变store里数据需要调用dispatch 
//把改变的方法写在这里
	return {
	handleInputFocus(){
	const action ={ type:'search_focus'}
	};
	dispatch(action);//派发action
	},
	handleInputBlur(){
	const action ={ type:'search_blur'}
	};
	dispatch(action);//派发action
	}
	
}

现在我们开始转移数据
把原先this.state{}整个删除 然后里面的数据放到reducer.js里的defaultState里

//store/reducer.js
const defaultState ={
	focused:false
};
export default (state= defaultState ,action) =>{
if(action.type==='search_focus'){return{
focused:true;
}}
if(action.type==='search_blur'){return{
focused:false;
}}
return state;
}

之前改变state的两个函数没有意义了,删除它
handleInputFocus () {}
handleInputBlur(){}
进而也删掉constructor

还可以装一个方便看数据变化等的插件Redux DevTools Extension
要想使用这个插件
需要增加compose conposeEnhancers 这两个东西

为了方便redux查询相关代码 不能让reducer这个文件太大 进行拆分所以我们在header下创建store和reducer 只放专属的代码
combineReducers可以把小的reducer合并成大的
即store/reducer.js

import { combineReducers } from 'redux';//将小的reducer合并成大的reducer
//接下来引入小笔记本
import headerReducer from '../common/header/store/reducer';

写法一:
export default combineReducers ({
	header: headerReducer//整个state多了一层所以之前的函数mapStateToProps中state.focused也要改称state.header.focused 
});
写法二:
const reducer = combineReducers({
	header:headerReducer
});
export default reducer;

再引入这个小reducer时路径太长怎么办呢?
解决:小的store下建立index.js:

import reducer from './reducer';
export {reducer};
则在大store的reducer.js下只需修改成
import {reducer} from '../common/header/store';//在这个store下加入index.js 并导出{reducer}

若与下面reducer名冲突,我们可以起一个别名:
import {reducer as headerReducer}from '../common/header/store';

为使代码更规范,
在小的store下创建actionCreators.js

export const searchFocus =()=> ({
	type:'search_focus';
})

export const searchBlur =()=> ({
	type:'search_blur';
})

store/index.js

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;

header/index.js下
起别名
import * as actionCreators from './store/actionCreators';
所以要改为

const mapDispatchToProps =( dispatch )=> {//组件改变store里数据需要调用dispatch 
//把改变的方法写在这里
	return {
	handleInputFocus(){
	const action = actionCreators.searchFocus();
	};
	dispatch(action);//派发action
	以上还可以改成
	dispatch(actionCreators.searchFocus());
	},
	handleInputBlur(){
	dispatch(actionCreators.searchBlur());
	};
	}
	
}

然后把actioncreators里面的type都改为常量
在里面建立constants.js

export const SEARCH_FOCUS ='header/SEARCH_FOCUS';
export const SEARCH_BLUR ='header/SEARCH_BLUR';
export const CHANGE_LIST ='header/CHANGE_LIST';

之后就可以将actionCreators.js里的type替换成常量了如constants.SEARCH_FOCUS

小reducer.js里面

import * as constants from './constants';
const defaultState ={
	focused:false
};
export default (state= defaultState ,action) =>{
if(action.type===constants.SEARCH_FOCUS){return{
focused:true;
}}
if(action.type===constants.SEARCH_BLUR){return{
focused:false;
}}
return state;
}

在小store的index.js中可以操作使得只需要引用index一种即可

import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer ,actionCreators,constants };

在header/index.js中
import {ationCreators }from './store;

拆分完毕!

一定不能修改reducer中的state
为了防止修改 我们使用immutable.js库
immutable :不可改变的

首先安装immutable (搜索github
在小store里的reducer中引入

import { fromJS } from 'immutable';

然后把js对象转化成immutable对象

const defaultState =fromJS ({
	focused:false;
});

然后去小store下的index.js中
此时state.header已经是immutable的数据了
则修改成
focused: state.header.get(‘focused’)去获取属性
然后把之前的if…focused值改变的代码变成

return state.set('focused',true);

immutable对象的set方法会结合之前的值和新的值返回一个全新的对象而不会去改之前的对象


focused: state.header.get(‘focused’)
但我们想要用对象的方式来写呢?
引入redux-immutable

在大store/reducer.js里
修改为

import {combineReducers }from 'redux-immutable';
然后修改header/index.js
mapStateToProps :
focused:state.get('header').get('focused')
或者另一个写法
focused:state.getIn(['header','focused']);//是一个数组的两项

鼠标聚焦搜索框后出现热搜 我们来做这个

header/index.js
import { HeaderWrapper ,Logo,Nav,NavSearch,Addition,Button,SearchWrapper,SearchInfo,SearchInfoTitle,SearchInfoSwitch,SearchInfoItem,SearchInfoList} from './style';
import { CSSTransition }from 'react-transition-group';
const Header =(props) =>{return和 里面的内容全部放到这里 中间的this.props替换为props}
//提升性能
const getListArea =(show) =>{
if(show){ return ( <SearchInfo>
		 	<SearchInfoTitle>
		 	热门搜索
		      <SearchInfoSwitch>
		      换一批
		      </SearchInfoSwitch>
		 	</SearchInfoTitle>
		 	<SearchInfoList>
		 	<SearchInfoItem>教育 </SearchInfoItem>
		 	<SearchInfoItem>教育 </SearchInfoItem>
		 	<SearchInfoItem>教育 </SearchInfoItem>
		 	</SearchInfoList>
		 </SearchInfo>) 	}
else
{return null;}
}
class Header extends Component {
	constructior(props){
		super(props};
		this.state ={ focused:false;}
		this.handleInputFocus = this.handleInputFocus.bind(this);
		this.handleInputBlur = this.handleInputBlur.bind(this);
		}
	render(){
		return (
<HeaderWrapper>
	<Logo href='/' />
	<Nav>
		<NavItem className='left active'>首页</NavItem>
		<NavItem className='left'>下载App</NavItem>
		<NavItem className='right'>登陆</NavItem>
		<NavItem className='right'>Aa</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}//离开聚焦时
		</NavSearch>
		</CSSTransition>
		<i className={this.props.focusd?'focused iconfont':' '}>
		 &#xe614 
		 </i>
		{getListArea(props.focused)}//调用方法
		</SearchWrapper>
	</Nav>
	<Addition>
		<Button className='reg'>注册</Button>
		<Button className='writting'> 写文章</Button>
	</Addition>
</HeaderWrapper>
)
}
handleInputFocus () {
	this.setState({
	focused:true})
}
handleInputFocus () {
	this.setState({
	focused:false})
}

header/style.js
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);
`;
export const SearchInfoTitle = styled.div`
	margin-top:20px;
	margin-bottom:15px;
	line-height:20px;
	font-size:14px;
	color:#969696;
`;
export const SearchInfoList = styled.div`
	overflow:hidden;
`;

export const SearchInfoSwitch = styled.span`
	float:right;
	font-size:13px;
`;
export const SearchInfoItem = styled.a`
	display:block;
	float:left;
	font-size:12px;
	padding:0 5px;
	margin-right:10px;
	margin-bottom:10px;
	line-height:20px;
	border: 1px solid #ddd;
	color:#787878;
	border-radius:3px;
`;

对于一个大型项目来说,无状态组件里内容越来越庞大会不便维护。

header/index.js
import { HeaderWrapper ,Logo,Nav,NavSearch,Addition,Button,SearchWrapper,SearchInfo,SearchInfoTitle,SearchInfoSwitch,SearchInfoItem,SearchInfoList} from './style';
import { CSSTransition }from 'react-transition-group';

class Header extends Component {
	getListArea () {
		if(this.props.focused){ 
		return ( 
		<SearchInfo>
		 	<SearchInfoTitle>
		 	热门搜索
		      <SearchInfoSwitch>
		      换一批
		      </SearchInfoSwitch>
		 	</SearchInfoTitle>
		 	<SearchInfoList>
		 	{
		 	this.props.list.map((item) =>{
		 	return <SearchInfoItem key={item}> {item} </SearchInfoItem>
		 	})
		 	}
		 	</SearchInfoList>
		 </SearchInfo>) 	}
		else
		{return null;}
	}
	
	render(){
		return (
<HeaderWrapper>
	<Logo href='/' />
	<Nav>
		<NavItem className='left active'>首页</NavItem>
		<NavItem className='left'>下载App</NavItem>
		<NavItem className='right'>登陆</NavItem>
		<NavItem className='right'>Aa</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}//离开聚焦时
		</NavSearch>
		</CSSTransition>
		<i className={this.props.focusd?'focused iconfont':' '}>
		 &#xe614 
		 </i>
		{this.getListArea()}//调用方法
		</SearchWrapper>
	</Nav>
	<Addition>
		<Button className='reg'>注册</Button>
		<Button className='writting'> 写文章</Button>
	</Addition>
</HeaderWrapper>
)
}

const mapStateToProps =( state )=> {//store里数据state如何映射到props
	return {
	focused:state.getIn('header','focused']),//immutable对象得到
	list:state.getIn('header','list'])
	}
	//然后把this.state.focused全部替换为this.props...
}

const mapDispatchToProps =( dispatch )=> {//组件改变store里数据需要调用dispatch 
//把改变的方法写在这里
	return {
	handleInputFocus(){
	dispatch(actionCreators.getList());
	dispatch(actionCreators.searchFocus());//action由actioncreators创建
	},
	handleInputBlur(){
	dispatch(actionCreators.searchBlur());
	}
}
}
export default connect (mapStateToProps,mapDispatchToProps)(Header);

继续把搜索框中的数据也要放到header里,所以在header/store/reducer.js里增加数据list

小reducer.js里面

import * as constants from './constants';
import { fromJS } from 'immutable';
const defaultState =fromJS({
	focused:false
	list:[]
});
export default (state= defaultState ,action) =>{
if(action.type===constants.SEARCH_FOCUS){
return state.set('focused',true);
}
if(action.type===constants.SEARCH_BLUR){
return state.set('focused',false);
}
if(action.type===constants.CHANGE_LIST){
return state.set('list',action.data);
//这里注意immutable数组和普通数组类型不同,不可替换,这样写错误
}
return state;
}

然后我们需要在聚焦函数中加入请求 获取ajax数据(放入actions或Redux-saga) 这个函数在mapDispathToprops()中
在这个项目中统一使用redux-thunk 先安装它 他是redux中间件(store与action直接),在创建store时被使用,则我们在大store/index.js里引入

然后写获取数据的函数
actionCreators.js

import * as constants from './constants';
import axios from 'axios';
import {fromJS} from 'immutable';
export const searchFocus =()=> ({
	type:constants.SEARCH_FOCUS;
})

export const searchBlur =()=> ({
	type:constants.SEARCH_BLUR;
})

const changeList =(data) =>({//所以要把Data变成immutable数组
	type:constants.CHANGE_LIST,
	data:fromJS(data)
})

export const getList = () => {
	return (dispatch) => {
	axios.get('/api/headerList.json').then((res) =>{
	const data=res.data;
	写法一:
	const action= changeList(data.data);
	dispatch(action);
	写法二:
	dispatch(changeList(data.data));
	}).catch(()=>{
		console.log('error');
		})
	}
};

然后在小reducer里进行增添操作。
然后需要在搜索热点框中展示出来。返回header/index.js


代码优化微调

this.props. ***在组件的index.js中实在太麻烦,我们可以
const { focused , list } =this.props;

reducer.js中 的许多if可以换成switch
switch(action.type){
	case constants.SEARCH_FOCUS :
		return state.set('focused',true);
	case ....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值