仿簡書PC的react項(xiàng)目(涵蓋主流 React 開發(fā)相關(guān)最新技術(shù)點(diǎn))

此項(xiàng)目github地址:https://github.com/CoderZF/jianshu-pc

目錄

技術(shù)棧:

react + redux + redux-thunk(讓redux支持異步的中間件) + webpack + react-router + ES6/7/8 + axios + react-transition-group(react動(dòng)畫庫)+ react-loadable(使組件按需載) + styled-components(css組件化) + immutable.js

運(yùn)行打包(nodejs 6.0+):

 git clone https://github.com/CoderZF/jianshu-pc.git

 cd jianshu-pc

 npm i  或者運(yùn)行  yarn(推薦)
  
 npm start

 npm run build (發(fā)布)

項(xiàng)目結(jié)構(gòu)及技術(shù)點(diǎn)介紹:

該項(xiàng)目由 Create React App 搭建.

項(xiàng)目結(jié)構(gòu):

jianshu-pc
│   README.md
│   package.json
└───src
│   │   App.js
│   │   idnex.js
│   │   style.js
│   └───common
│   │      └───header
│   │             │   index.js
│   │             │   style.js
│   │             └───store   
│   │                  │   actionCreators.js
│   │                  │   constants.js  
│   │                  │   index.js  
│   │                  │   reducer.js  
│   └───pages
│   │      └───detail
│   │      │       │   index.js
│   │      │       │   style.js
│   │      │       │   loadable.js
│   │      │       └───store   
│   │      │           │   actionCreators.js
│   │      │           │   constants.js  
│   │      │           │   index.js  
│   │      │           │   reducer.js  
│   │      └───home
│   │      │       │   index.js
│   │      │       │   style.js
│   │      │       └───store   
│   │      │       │      actionCreators.js
│   │      │       │      constants.js  
│   │      │       │      index.js  
│   │      │       │      reducer.js   
│   │      │       └───components   
│   │      │           │   List.js
│   │      │           │   Recommend.js  
│   │      │           │   Topic.js  
│   │      │           │   Writer.js   
│   │      └───login
│   │      │       │   index.js
│   │      │       │   style.js
│   │      │       └───store   
│   │      │           │   actionCreators.js
│   │      │           │   constants.js  
│   │      │           │   index.js  
│   │      │           │   reducer.js   
│   │      └───write
│   │      │       │   index.js
│   │      │       │   style.js
│   └───statics
│   │      │   logo.png
│   │      │   ...
│   │      └───iconfont
│   │             │   iconfont.eot
│   │             │   iconfont.js
│   │             │   ...
│   └───store
│   │      │   index.js
│   │      │   reducer.js
│        
└───public
    │   ...

styled components:

使用styled components,可將組件分為邏輯組件和展示組件,邏輯組件只關(guān)注邏輯相關(guān)的部分缠借,展示組件只關(guān)注樣式。通過解耦成兩種組件,可以使代碼變得更加清晰可維護(hù)亡笑。當(dāng)邏輯有變化,如后臺(tái)拉取的數(shù)據(jù)的格式有所變化時(shí)横朋,只需關(guān)注并修改邏輯組件上的代碼仑乌,展示組件的代碼不用動(dòng)。而當(dāng)UI需要變化時(shí),只需改變展示組件上的代碼晰甚,并保證展示組件暴露的props接口不變即可衙传。邏輯組件和展示組件各司其職,修改代碼時(shí)錯(cuò)誤發(fā)生率也會(huì)有所減少厕九。

import { injectGlobal } from 'styled-components';

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

上面js可以看出全局公用樣式使用injectGlobal蓖捶,所有css寫在字符串模板中,vscode下載vscode-styled-components插件可支持語法高亮扁远。

 import styled from "styled-components";

 export const RecommendWrapper = styled.div`
  margin: 30px 0;
  width: 280px;
`;
 export const RecommendItem = styled.div`
  width: 280px;
  height: 50px;
  background: url(${props => props.imgUrl});
  background-size: contain;
`;
import { RecommendWrapper, RecommendItem } from '../style';

class Recommend extends PureComponent {
    render() {
        return (
            <RecommendWrapper>
                {
                    this.props.list.map((item) => {
                        return <RecommendItem imgUrl={item.get('imgUrl')} key={item.get('id')}/>
                    })
                }
            </RecommendWrapper>
        )
    }
}

上面2個(gè)js就是styled components最常用的使用方法俊鱼,將視圖和邏輯徹底分離。

使用iconfont嵌入圖標(biāo)

image.png

image.png

動(dòng)畫庫的使用

react-transition-group是react官方提供的動(dòng)畫庫穿香,也是之前兩個(gè)的合體版本亭引,此動(dòng)畫庫總共提供三個(gè)組件Transition,CSSTransition和TransitonGroup皮获。
本項(xiàng)目為實(shí)現(xiàn)輸入框在聚焦和失去焦點(diǎn)時(shí)其長度的變化焙蚓,使用了CSSTransition這個(gè)組件。

           <CSSTransition in={focused} timeout={200} classNames="slide">
             <NavSearch
               className={focused ? "focused" : ""}
               onFocus={() => handleInputFocus(list)}
               onBlur={handleInputBlur}
             />
           </CSSTransition>
 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 0.2s ease-out;
          }
          &.slide-enter-active {
            width: 240px;
          }
          &.slide-exit {
            transition: all 0.2s ease-out;
          }
          &.slide-exit-active {
            width: 160px;
          }
`; 

CSSTransition包裝的組件會(huì)給其組件自動(dòng)包裝不同狀態(tài)的類名洒宝,如上slide-enter购公,slide-enter-active,slide-exit雁歌,slide-exit-active 就是其根據(jù)classNames-xxx自動(dòng)掛載的宏浩。

使用react-redux及其中間件

首先為根組件用react-redux提供的Provider包裹,其目的就是讓整個(gè)項(xiàng)目的組件可以使用store靠瞎。

class App extends Component {
  render() {
    return (
    <Provider store={store}>
        <BrowserRouter>
            <div>
            <Header />
                <Route path='/' exact component={Home}></Route>
            <Route path='/login' exact component={Login}></Route>
            <Route path='/write' exact component={Write}></Route>
                <Route path='/detail/:id' exact component={Detail}></Route>
            </div>
        </BrowserRouter>
      </Provider>
    );
  }
}

然后讓組件通過connect連接store比庄,connect第一次調(diào)用的兩個(gè)參數(shù)分別是store和dispatch對(duì)其組件props的映射回調(diào)函數(shù)

import { connect } from "react-redux";
...
class Header extends Component {
    ...
}
...
export default connect(
  mapStateToProps,
  mapDispathToProps
)(Header);

代碼和性能優(yōu)化:

this綁定優(yōu)化

  1. 當(dāng)使用bind()綁定時(shí),最好把所有需要綁定的方法都放在構(gòu)造函數(shù)constructor中乏盐,這樣就僅需要綁定一次就可以佳窑,避免每次渲染時(shí)都要重新綁定,函數(shù)在別處復(fù)用時(shí)也無需再次綁定父能。
import React, {Component} from 'react'

class Test extends React.Component {
    constructor (props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
    }

    handleClick (e) {
    }

    render () {
        return (
            <div>
                <button onClick={ this.handleClick }>Say Hello</button>
            </div>
        )
    }
}
  1. 箭頭函數(shù)則會(huì)捕獲其所在上下文的this值神凑,作為自己的this值,使用箭頭函數(shù)就不用擔(dān)心函數(shù)內(nèi)的this不是指向組件內(nèi)部了何吝「任可以按下面這種方式使用箭頭函數(shù):
class Test extends React.Component {
    constructor (props) {
        super(props)
        this.state = {message: 'Allo!'}
    }

    handleClick (e) {
        console.log(this.state.message)
    }

    render () {
        return (
            <div>
                <button onClick={ ()=>{ this.handleClick() } }>Say Hello</button>
            </div>
        )
    }
}

使用這個(gè)語法有個(gè)問題就是每次 Test 渲染的時(shí)候都會(huì)創(chuàng)建一個(gè)不同的回調(diào)函數(shù)。在大多數(shù)情況下爱榕,這沒有問題瓣喊。然而如果這個(gè)回調(diào)函數(shù)作為一個(gè)屬性值傳入低階組件,這些組件可能會(huì)進(jìn)行額外的重新渲染黔酥。我們通常建議在構(gòu)造函數(shù)中綁定或像下面代碼使用屬性初始化器語法來避免這類性能問題藻三。

class Test extends React.Component {
    constructor (props) {
        super(props)
        this.state = {message: 'Allo!'}
    }

    handleClick = (e) => {
        console.log(this.state.message)
    }

    render () {
        return (
            <div>
                <button onClick={ this.handleClick }>Say Hello</button>
            </div>
        )
    }
}

使用無狀態(tài)組件提高性能

如此組件沒有狀態(tài)的影響或者僅僅純靜態(tài)展示時(shí)八匠,完全可以用無狀態(tài)組件來替代有狀態(tài)組件,因其除render無任何其他生命周期方法且僅僅返回的是個(gè)函數(shù)趴酣,無實(shí)例化過程梨树,大大提升了性能。

import React, { PureComponent } from 'react';
import { WriterWrapper } from '../style';

class Writer extends PureComponent {

    render() {
        return (
            <WriterWrapper>HomeWork</WriterWrapper>
        )
    }
}

export default Writer;

上面組件就可以完全改裝成如下無狀態(tài)組件岖寞。

import React, { PureComponent } from "react";
import { WriterWrapper } from "../style";

const Writer = () => <WriterWrapper>HomeWork</WriterWrapper>;

export default Writer;

immutable.js與redux結(jié)合使用

當(dāng)我們對(duì)一個(gè)Immutable對(duì)象進(jìn)行操作的時(shí)候抡四,ImmutableJS基于哈希映射樹(hash map tries)和vector map tries,只clone該節(jié)點(diǎn)以及它的祖先節(jié)點(diǎn)仗谆,其他保持不變指巡,這樣可以共享相同的部分,大大提高性能隶垮。在對(duì)Immutable對(duì)象的操作均會(huì)返回新的對(duì)象藻雪,所以使用redux的reducer中就不需要總是想著不能修改原state,因?yàn)閷?duì)Immutable對(duì)象的操作返回就是新的對(duì)象狸吞,且比普通js深拷貝產(chǎn)生的性能消耗要低得多勉耀。
我在項(xiàng)目中也是大量使用immutable.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;
    }
}
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 getList = () => {
    return (dispatch) => {
        axios.get('/api/headerList.json').then((res) => {
            const data = res.data;
            dispatch(changeList(data.data));
        }).catch(() => {
            console.log('error');
        })
    }
};

避免無意義的網(wǎng)絡(luò)請(qǐng)求

比如在請(qǐng)求熱門搜索提示項(xiàng)的時(shí)候,只有當(dāng)size是0的時(shí)候我才去發(fā)送請(qǐng)求蹋偏。

  const mapDispathToProps = dispatch => {
  return {
    handleInputFocus(list) {
      list.size === 0 && dispatch(actionCreators.getList());
      dispatch(actionCreators.searchFocus());
    },
  ...
    };

異步操作代碼拆分優(yōu)化

在UI組件中因盡量減少業(yè)務(wù)邏輯操作便斥,像與服務(wù)器交互的大量代碼都應(yīng)該解耦出來,所以結(jié)合redux-thunk的使用將大量的網(wǎng)絡(luò)請(qǐng)求代碼寫在action中就解決了這一問題威始。
下面是home頁的actionCreators.js枢纠,當(dāng)前模塊的所有action和網(wǎng)絡(luò)請(qǐng)求都在此文件中

import axios from 'axios';
import * as constants from './constants';
import { fromJS } from 'immutable';

const changHomeData = (result) => ({
    type: constants.CHANGE_HOME_DATA,
    topicList: result.topicList,
    articleList: result.articleList,
    recommendList: result.recommendList
});

const addHomeList = (list, nextPage) => ({
    type: constants.ADD_ARTICLE_LIST,
    list: fromJS(list),
    nextPage
})

export const getHomeInfo = () => {
    return (dispatch) => {
        axios.get('/api/home.json').then((res) => {
            const result = res.data.data;
            dispatch(changHomeData(result));
        });
    }
}

export const getMoreList = (page) => {
    return (dispatch) => {
        axios.get('/api/homeList.json?page=' + page).then((res) => {
            const result = res.data.data;
            dispatch(addHomeList(result, page + 1));
        });
    }
}

export const toggleTopShow = (show) => ({
    type: constants.TOGGLE_SCROLL_TOP,
    show
})

這樣在組件中就可以輕松的去調(diào)用網(wǎng)絡(luò)請(qǐng)求李茫,然后將返回結(jié)果發(fā)送給reducer進(jìn)行處理

import React, { PureComponent } from 'react';
import { ListItem, ListInfo, LoadMore } from '../style';
import { connect } from 'react-redux';
import { actionCreators } from '../store';
import { Link } from 'react-router-dom';

class List extends PureComponent {
    render() {
        const { list, getMoreList, page } = this.props;
        return (
            <div>
                {
                    list.map((item, index) => {
                        return (
                            <Link key={index} to={'/detail/' + item.get('id')}>
                                <ListItem >
                                    <img alt='' className='pic' src={item.get('imgUrl')} />
                                    <ListInfo>
                                        <h3 className='title'>{item.get('title')}</h3>
                                        <p className='desc'>{item.get('desc')}</p>
                                    </ListInfo>
                                </ListItem>
                            </Link>
                        );
                    })
                }
                <LoadMore onClick={() => getMoreList(page)}>更多文字</LoadMore>
            </div>
        )
    }
}

const mapState = (state) => ({
    list: state.getIn(['home', 'articleList']),
    page: state.getIn(['home', 'articlePage'])
});

const mapDispatch = (dispatch) => ({
    getMoreList(page) {
        dispatch(actionCreators.getMoreList(page))
    }
})

export default connect(mapState, mapDispatch)(List);

使用PureComponent

繼承Component的普通組件慢逾,使用react-redux的connect連接了store,那么只要store內(nèi)的數(shù)據(jù)發(fā)生改變就會(huì)讓所有連接的組件觸發(fā)render缘揪,這樣就會(huì)產(chǎn)生不必要的渲染開銷脓斩,當(dāng)然使用shouldComponentUpdate也可以阻止不必要的渲染木西,但這樣的話每個(gè)組件都要寫同樣的shouldComponentUpdate方法;繼承PureComponent的組件正好解決了這一痛點(diǎn)俭厚,默認(rèn)實(shí)現(xiàn)的shouldComponentUpdate户魏。


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驶臊,一起剝皮案震驚了整個(gè)濱河市挪挤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌关翎,老刑警劉巖扛门,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纵寝,居然都是意外死亡论寨,警方通過查閱死者的電腦和手機(jī)星立,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葬凳,“玉大人绰垂,你說我怎么就攤上這事』鹧妫” “怎么了劲装?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昌简。 經(jīng)常有香客問我占业,道長,這世上最難降的妖魔是什么纯赎? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任谦疾,我火速辦了婚禮,結(jié)果婚禮上犬金,老公的妹妹穿的比我還像新娘念恍。我一直安慰自己,他們只是感情好晚顷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布樊诺。 她就那樣靜靜地躺著,像睡著了一般音同。 火紅的嫁衣襯著肌膚如雪词爬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天权均,我揣著相機(jī)與錄音顿膨,去河邊找鬼。 笑死叽赊,一個(gè)胖子當(dāng)著我的面吹牛恋沃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播必指,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼囊咏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了塔橡?” 一聲冷哼從身側(cè)響起梅割,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葛家,沒想到半個(gè)月后户辞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡癞谒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年底燎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刃榨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡双仍,死狀恐怖枢希,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朱沃,我是刑警寧澤晴玖,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站为流,受9級(jí)特大地震影響呕屎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜敬察,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一秀睛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莲祸,春花似錦蹂安、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缴阎,卻和暖如春允瞧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛮拔。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工述暂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人建炫。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓畦韭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肛跌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子艺配,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容