简书项目总结

1. 插件和脚手架

脚手架: create-react-app

插件

  1. redux
  2. react-redux
  3. immutable
  4. redux-immutable(也提供了combineReducers函数)
  5. axios
  6. redux-thunk
  7. styled-components

2. 目录

简书项目总结_第1张图片
简书项目总结_第2张图片
public放静态资源,和前端的数据模拟

3. 调试工具和reduc-thunk

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;

4. 合并reducer

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
})

5. styled-components使用

全局样式

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;
`

6. statics

简书项目总结_第3张图片
阿里巴巴字体库解压出来的有很多文件,只需要上面的字体文件
修改:

  1. iconfont.css改成js后缀的
  2. 进入iconfont.js用styled-components语法写成全局样式
  3. 给里面加上相对路径./
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;
    }
`

7. 单个模块的目录

如果页面过于简单可以不用components目录
简书项目总结_第4张图片
简书项目总结_第5张图片

8. 单个组件的代码

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);

8. 单个组件的store

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';

9. immutable语法

(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])

这里表示拿detail模块下的store中的title字段
简书项目总结_第6张图片

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]})
		}
	}
}

10. 换一换小动画

简书项目总结_第7张图片


    热门搜索
     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));
    }
},

你可能感兴趣的:(React)