styled-components
css文件是全局引用的,一个组件中引用了,所有组件都可以用
建议用第三方组件管理css
yarn add styled-components
import { createGlobalStyle } from 'styled-components'
export const injectGlobal = createGlobalStyle`
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
`
以上为新版写法
reset.css 在所有浏览器上样式统一
把各个浏览器对标签自身样式进行统一
到网站复制代码,放到style.js里
用styled-components完成header组件布局
可以用chrome控制台那个带箭头的长方形按钮看页面各个原始的尺寸
两种跳转方式
export const Logo = styled.a.attrs({
href: '/'
})`
position: absolute;
top: 0;
left: 0;
display: block;
width: 100px;
height: 56px;
background: url(${logoPic});
background-size: contain;
`;
点击a标签,会往首页跳转
<Logo href: '/'/>
两种写法
iconfont
使用iconfont嵌入头部图标
iconfont.cn
把iconfont.css改成js文件,按照styled-components全局变量的方式写IconfontStyle,按组件引用,最后按官方文档写个
- .标签。
-
过渡动画
做过渡动画
给Header类加个constructor(props){ super(props); this.state = { focused: false } } <NavSearch className = {this.state.focused ? 'focused' : ''} ></NavSearch>
NavSearch里加样式
&.focused { width: 200px; } <i className={this.state.focused ? 'focused iconfont' : 'iconfont'}></i>
SearchWrapper里的.iconfont修改
加了handleInputFocus 和handleInputFlur
结合动画
yarn add react-transition-group <CSSTransition in={this.state.focused} timeout={200} classNames="slide" ></CSSTransition>
classNames!!!
用redux和react-redux进行数据管理
使用React-Redux进行应用数据的管理
安装两个内容yarn add redux yarn add react-redux
import { Provider } from 'react-redux'; <Provider store = {store}> <Header/> </Provider>
这样Header才能拿到store里的数据
const mapDispatchToProps = (dispatch) => { return { handleInputFocus(){ const action = { type: 'search_focus' }; dispatch(action); }, handleInputBlur(){ const action = { type: 'search_blur' }; dispatch(action); } } }
改写之后
constructor(props){ super(props); this.state = { focused: false } this.handleInputFocus = this.handleInputFocus.bind(this) this.handleInputBlur = this.handleInputBlur.bind(this) }
这部分删除。
使用combineReducers完成对数据的拆分管理
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; import {compose}from ‘redux’
header文件下加一个reducer,放reducer的内容
src下的reducerimport { combineReducers } from 'redux'; import headerReducer from '../common/header/store/reducer'; export default combineReducers({ header: headerReducer })
组合多个不同组件的reducer
这时动画效果没有了
因为数据结构多了一层headerconst mapStateToProps = (state) => { return { focused: state.header.focused } }
加上就好了
header的store 下加一个index.js
import reducer from './reducer'; export { reducer }
暴露reducer
总reducer中改写成如下代码。import { combineReducers } from 'redux'; import { reducer as headerReducer } from '../common/header/store'; const reducer = combineReducers({ header: headerReducer }) export default reducer;
actionCreators与constants的拆分
import * as actionCreators from './store/actionCreators';
用ES6的语法导入
使用immutable.js来管理store中的数据
第三方模块,帮助我们生成immutable对象,防止改state。
yarn add immutable import { fromJS } from 'immutable'; JS 对象转换成immutable对象 focused: state.header.focused
改成:
state.header.get(‘focused’)
return state.set('focused',true);
用redux-immutable统一数据格式
axios Ajax
Ajax获取推荐数据
const mapDispatchToProps = (dispatch) => { return { handleInputFocus(){ dispatch(actionCreators.getList()); dispatch(actionCreators.searchFocus()); }, handleInputBlur(){ dispatch(actionCreators.searchBlur()); } } }
actionCreators
export const getList = () => { return (dispatch) => { console.log(123) } };
import axios
index.js改成
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;
public下建 api文件 建headerLIst.json
src下找不到会去public下找,如果挨个查找。
上线的时候删除API文件就行if(action.type === constants.CHANGE_LIST){ return state.set('list', action.data); }
data要变成immutable对象
所以在createAction里要改写
const changeList = (data) => ({ type: constants.CHANGE_LIST, data: fromJS(data) })
浏览器打开redux,切换到state,刷新,点击搜索框,state里展示了我们json中的内容。
redux-thunk 的作用<SearchInfoList> { this.props.list.map((Item) => { return <SearchInfoItem key = {Item}>{Item}</SearchInfoItem> }) } </SearchInfoList>
循环展示
代码调优 actionCreator export统一放在最下
const { focused, list } = this.props;
focused就不用写成this.props.focused.
换一批功能
const defaultState = fromJS({ focused: false, list: [], page: 1, totalPage: 1 }); 加page和totalPage两个数据项 const changeList = (data) => ({ type: constants.CHANGE_LIST, data: fromJS(data), totalPage: Math.ceil(data.length / 10) }) case constants.CHANGE_LIST: return state.set('list', action.data).set('totalPage', action.totalPage);
点换一批的时候input失焦
header里的index.js
const newList = list.toJS(); const pageList = []; for(let i = ((page - 1)* 10); i < (page * 10); i++){ console.log(newList[i]) pageList.push( <SearchInfoItem key ={newList[i]}>{newList[i]}</SearchInfoItem> ) }
直接输出10次undefined
处理一下
if(newList.length){ for(let i = ((page - 1)* 10); i < (page * 10); i++){ pageList.push( <SearchInfoItem key ={newList[i]}>{newList[i]}</SearchInfoItem> ) } } return state.merge({ list: action.data, totalPage: action.totalPage })
代替
return state.set('list', action.data).set('totalPage', action.totalPage);
换页旋转动画效果实现
与前文不同,此处用css动画
.spin{ display: block; float: left; font-size: 12px; margin-right: 2px; transition: all .2s ease-in; transform-origin: center center; } 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)); } } <i ref={(icon) => {this.spinIcon = icon}} className='iconfont spin'></i>
React的难点是面向数据设计
多写几遍项目代码,熟练之后就是套路避免无意义的请求发送,提升组件性能
改一下handleInputFocus
(list.size === 0) && dispatch(actionCreators.getList());
鼠标到换一批时显示成手型
cursor: pointer
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; } `;