脚手架: create-react-app
插件
src/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;
src/store/reducer.js
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';
export default combineReducers({
header: headerReducer,
home: homeReducer,
login: loginReducer,
detail: detailReducer
})
reset.css
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
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;
}
`;
使用
import React, { Component } from 'react';
import store from './store';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Header from './common/header';
import Home from './pages/home';
import Detail from './pages/detail/loadable';
import Login from './pages/login';
import Write from './pages/write';
// 全局样式
import { GlobalStyle } from './style.js';
import { GlobalIconfont } from './statics/iconfont/iconfont';
class App extends Component {
render() {
return (
);
}
}
style.js
import styled from 'styled-components';
// 清除浮动
// margin:0 auto和overflow: hidden冲突的时候使用
export const HomeWrapper = styled.div`
width: 960px;
margin: 0 auto;
&::after {
content: '';
height: 0;
display: block;
clear: both;
visibility: hidden;
}
`
// banner-img是这个div包裹的元素的类名
// img-box是这个div上面的类名
export const HomeLeft = styled.div`
float: left;
width: 625px;
margin-left: 15px;
margin-top: 30px;
.banner-img {
width: 625px;
height: 270px;
}
&.img-box {
width: 300px
}
`
// input框placeholder字体颜色
export const NavSearch = styled.input.attrs({
placeholder: '搜索'
})`
font-size: 14px;
color: #666;
&::placeholder {
color: #999;
};
`
import styled from 'styled-components';
// 这里引用图片,使用的时候用模板字符串语法${}
import LogoPic from '../../statics/logo.png';
export const Logo = styled.a.attrs({
href: '/'
})`
position: absolute;
left: 0;
top: 0;
display: block;
width: 100px;
height: 56px;
background-image: url(${LogoPic});
background-size: contain;
`
阿里巴巴字体库解压出来的有很多文件,只需要上面的字体文件
修改:
import { createGlobalStyle } from 'styled-components';
export const GlobalIconfont = createGlobalStyle`
@font-face {font-family: "iconfont";
src: url('./iconfont.eot?t=1587890215838'); /* IE9 */
src: url('./iconfont.eot?t=1587890215838#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAP0AAsAAAAACDgAAAOnAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDMgqDWIMtATYCJAMUCwwABCAFhG0HRhs3B8guBuy24jDbKHQ/enFWZvBE44F+LX0fdrP5dwFUoBgdj4+KjoqKAhqPKpEOKmRlx5eFv79v0x7SGYZ01VmziitSsciHkjhhxdMVPZnryYSsiF6XzhDADRAAmjoXIur/53BpEyiQ+S3LZY7BvawA4573AltjHB7oALlzvmHsJvIgFxNQTBgHO84rYYGSgVYB4UKWzEAllHJFZtAI6oKBOYZ3CNHEV/w2gDfv58M/iIoiqhLQ8eQ81wYZP6mvlUTACEADRQBrOD2UYSQsAplwWWi8QATBRYRi/R1pHygaUfxJfe78Oe5rpWFgczckLjBElIf554UaSG+DfT6U+UlhCpGfnZlFojieQuJ3Jfta0itQBgo/At9V1RhwoyjJ0kAEnZtmBN1u37EXn8DjxyzGclgwKLRpPB9PCDypFC5p+odAh7P6aSwvrD4/mtt7gLXxUif6NJSlHabaulUD6Q29a8znTUsASiif9OK7WJsgtHmxKdyJPRB4bpfrGPbArzqxoQe38/Rm91B6TeOH3vLJE077zAe/CE8+cY8/6hdqa1+F+zPxBSdOLMAzZoEvZVwk29vzxfrsrfrsRL7RaAPwHdTVZ8a+W1pa+ulzSQlx4umq1ZdTJKTr5qf8rxPzH4zKeYA6GpZszZ5WzuFZFjktSOppfvVmQjgWchDmGc3LHRpNxRlJH5sE9VafHnrf4f+GD4SUlVlO49z+P7NcHfedD6nYSmgauRXSqGIrEfEJRudtVddIAvC/QRqKJa+TeMrf+GNxwi5AZwcJ6Q6gYyQC6BwinhOBt/w72v/rNjR3SpkyZfDjljzSs3cCJDQTwqsSacx6p+UyBVnlAFWVzSzM+TlwldUwkdeX9Satg14PYx4BkT5oVBYPUZdxSBqTyIxdhErLJtQae1AsyB/eMkRBCLkb8+ZxEPQ7DFGvD0j63UFm7CuojPoFtf4IKE5DtwlbppPpbAWZVWRjLFMY2epxi0ij0n5mFZImOM1KnOPheaT4ZQ8TGRaRjuQjN1KG6OKfKEWpqsiIisfF5HHLIafTw3gVjx1Z1bBWVfUmhYeLRXcKs3pcQDuiQMxUiA3DYgqGzMrDTaTFVXTm+6sgkgmczJSKnBY1D1H4yZUzIoWJaIDMF7ob5VxKdb+JJFFUKlFsJlJ4uDDyeD3ihEEPhre4nh1ipQrT2sHvlSRcKCQ2FYZNL3bd4xIowOZViCGFHKpQy1de2U14kRvPNNOi2S3ZzHaZKgAAAA==') format('woff2'),
url('./iconfont.woff?t=1587890215838') format('woff'),
url('./iconfont.ttf?t=1587890215838') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('./iconfont.svg?t=1587890215838#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
`
src/page/home/list.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actionCreators } from '../store';
import { Link } from 'react-router-dom';
import { ListItem, ListInfo, LoadMore } from '../style';
class List extends Component {
render() {
const { list, addMoreArticle, page } = this.props;
return (
{
list.map((item, index) => (
{item.get('title')}
{item.get('desc')}
))
}
addMoreArticle(page)}>加载更多
);
}
}
// 获取store中数据
const mapState = (state) => ({
list: state.getIn(['home', 'articleList']),
page: state.getIn(['home', 'currentPage']),
});
// 修改store中数据
const mapDispatch = (dispatch) => ({
addMoreArticle(page) {
dispatch(actionCreators.getMoreArticle(page))
}
});
export default connect(mapState, mapDispatch)(List);
home模块的store
(1)index.js这个store的入口,通过引入这个文件来引入其他文件
import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as constants from './constants';
export { reducer, actionCreators, constants };
(2)reducer.js 分成的小的reducer,最后会合并
import { fromJS } from 'immutable';
import * as constants from './constants';
const defaultState = fromJS({
topicList: [],
articleList: [],
recommendList: [],
currentPage: 1,
showScroll: false
})
// 把逻辑都提出来,封装成了函数
const getHomeList = (state, action) => {
return state.merge({
topicList: fromJS(action.data.topicList),
articleList: fromJS(action.data.articleList),
recommendList: fromJS(action.data.recommendList),
})
}
// 把逻辑都提出来,封装成了函数
const addArticleList = (state, action) => {
return state.merge({
articleList: state.get('articleList').concat(fromJS(action.data.articleList)),
currentPage: action.nextPage
})
}
export default (state = defaultState, action) => {
switch(action.type) {
case constants.GET_HOME_LIST:
return getHomeList(state, action);
case constants.Add_ACTICLE_LIST:
return addArticleList(state, action);
case constants.TOGGLE_TOP_SHOW:
return state.set('showScroll', action.show);
default:
return state
}
}
(3)actionCreators.js用来生成一个action
import axios from 'axios';
import * as constants from './constants';
const getHomeData = (data) => ({
type: constants.GET_HOME_LIST,
data
})
// 这是不需要导出的action,下面的异步逻辑里面使用的
const addArticleList = (data, nextPage) => ({
type: constants.Add_ACTICLE_LIST,
nextPage,
data,
})
// 正常的aciton,需要导出
export const toggleTopShow = (show) => ({
type: constants.TOGGLE_TOP_SHOW,
show
});
// 异步逻辑,redux-thunk
export const changeHomeDate = () => {
return (dispatch) => {
axios.get('/api/home.json').then(res => {
const data = res.data.data;
dispatch(getHomeData(data))
})
}
}
// 异步逻辑,redux-thunk
export const getMoreArticle = (page) => {
return (dispatch) => {
axios.get('/api/homeList.json?page=' + page).then(res => {
const data = res.data.data;
dispatch(addArticleList(data, page + 1))
})
}
}
(4)constants.js所有的actionType都写成常量,集中管理
home表示命名空间,不影响功能
export const GET_HOME_LIST = 'home/GET_HOME_LIST';
export const Add_ACTICLE_LIST = 'home/Add_ACTICLE_LIST';
export const TOGGLE_TOP_SHOW = 'home/TOGGLE_TOP_SHOW';
(1)fromJS把普通对象转Immutable对象
如果对象里面嵌套的有数据也会被转化
import { fromJS } from 'immutable';
const defaultState = fromJS({
title: '',
content: ''
});
(2)获取immutable类型的store
获取: obj.get(key)
连写: obj.get(key).get(key).get(key)
简写:obj.getIn([key, key, key])
const mapState = (state) => ({
title: state.get('detail).get('title')
})
简写
const mapState = (state) => ({
title: state.getIn(['detail', 'title'])
})
(3)修改immutable类型的store
设置: obj.set(key, value)
可以连写: obj.set(key, value).set(key, value)
简写:obj.merge({})
export default (state = defaultState, action) => {
switch(action.type) {
case constants.LOGIN:
return state.set('login', action.data)
case constants.LOGOUT:
return state.set('login', action.data)
default:
return state
}
}
简写
export default (state = defaultState, action) => {
switch(action.type) {
case constants.CHANGE_DETAIL:
return state.merge({
title: action.data.title,
content: action.data.content
})
default:
return state
}
};
(4)数组
immutable的数组用下标无法获取都元素,需要使用immutableArray.toJS()
这是immutable的数组自带的方法
const { list } = this.props;
let newList = list.toJS(); // toJS()方法
let pageList = [];
if (newList.length) {
for(let i = (page - 1) * 10; i < page * 10; i++) {
if (newList[i]) {
pageList.push({newList[i]} )
}
}
}
热门搜索
handlePageChange(page, totalPage, this.spinIcon)}
>
this.spinIcon = icon} className="iconfont">
换一批
handlePageChange(page, totalPage, spinIcon) {
// 取出它的transform: rotate
let originAngle = spinIcon.style.transform.replace(/[^0-9]/ig, '');
// 存在就把它转成整数,不存在就给个0
if (originAngle) {
originAngle = parseInt(originAngle)
} else {
originAngle = 0
}
// 每次加360
spinIcon.style.transform = 'rotate(' + (originAngle + 360) + 'deg)';
// 如果不是最后一页就+一页,是最后一页就跳到第一页
if (page < totalPage) {
dispatch(actionCreators.pageChagne(page + 1));
} else {
dispatch(actionCreators.pageChagne(1));
}
},