react服務端渲染框架Next.js踩坑(三)

vision1發表於2019-04-02

這節課我們來完成頭部、首頁的程式碼和使用axios進行資料請求。

原始碼

react服務端渲染框架Next.js踩坑(一)
react服務端渲染框架Next.js踩坑(二)
react服務端渲染框架Next.js踩坑(三)

一、頁面Header

CNode社群所有頁面的頭部都是一樣的,所以我們需要把頭部程式碼抽出來當成公用元件 Header。
我們在components目錄下建立資料夾Header,在Header下建立 index.js 和 style.less。

// ~components/Header/index.js
import React from 'react'
import style from './style.less'

const Header = () => (
  <div className={style.header}>
    header
  </div>
)

export default Header
複製程式碼
// ~components/Header/style.less
.header{
  width: 100%;
}
複製程式碼

接下來我們需要建立一個 Layout 用來集中管理我們的 Header、Footer還有一些 meta 標籤等。在components建立Layout目錄,Layout下建立 index.js。

// ~pages/_app.js
import Head from 'next/head'
import React from 'react'
import Header from '../Header'

const Layout = ({
  children,
  title = 'CNode社群',
}) => {
  return (
    <div>
      <Head>
        <title>{ title }</title>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta name="renderer" content="webkit" />
        <link rel="icon" href="/static/favicon.ico" mce_href="/static/favicon.ico" type="image/x-icon" />
        <link rel="stylesheet" href="/static/css/reset.css" />
      </Head>
      <Header />
      { children }
    </div>
  )
}

export default Layout
複製程式碼

我們在這個元件裡配置了 header、title、favicon和公用的reset.css。favicon和reset.css都放在static目錄。

然後編輯 /pages/_app.js 檔案,把 Layout 整合進去。

// ~pages/_app.js
import App, { Container } from 'next/app'
import React from 'react'
import { Provider } from 'react-redux'
import withRedux from 'next-redux-wrapper'
import makeStore from '../store'
import Layout from '../components/Layout'

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {}
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }
    return { pageProps }
  }

  componentDidMount() {}
  // 在 Layout 傳入引數 title,使每個頁面可以設定不同的title。
  render() {
    const { Component, pageProps, store } = this.props
    return (
      <Container>
        <Provider store={store}>
          <Layout
            title={pageProps.title}
          >
            <Component {...pageProps} />
          </Layout>
        </Provider>
      </Container>
    )
  }
}

export default withRedux(makeStore)(MyApp);
複製程式碼

這樣我們的header元件就出現在頁面上了。
接下來我們根據CNode官網來寫Header的樣式。

// ~components/Header/index.js
import React from 'react'
import Link from 'next/link'
import style from './style.less'

const Header = () => (
  <div className={style.header}>
    <div className={style.headerInner}>
      <div className={`${style.container} clearfloat`}>
        <Link href="/">
          <a className={style.brand}>
            <img src="//static2.cnodejs.org/public/images/cnodejs_light.svg" alt="" />
          </a>
        </Link>
        <div className={`${style.nav} flex flex-align-center`}>
          <Link href="/">
            <a>首頁</a>
          </Link>
          <Link href="/">
            <a>新手入門</a>
          </Link>
          <Link href="/">
            <a>關於</a>
          </Link>
          <Link href="/">
            <a>API</a>
          </Link>
        </div>
      </div>
    </div>
  </div>
)

export default Header
複製程式碼
// ~components/Header/style.less
.header {
  margin-bottom: 0;
  z-index: 9;
  width: 100%;
  position: relative;
  background: #444;
  .headerInner {
    background: 0 0;
    border-radius: 0;
    border: none;
    box-shadow: none;
    width: 90%;
    margin: auto;
    padding: 5px;
    .container{
      width: 100%;
      min-width: 960px;
      margin: 0 auto;
      max-width: 1400px;
    }
  }
  .brand {
    display: block;
    width: 120px;
    float: left;
    padding: 3px 20px;
    height: 34px;
    line-height: 34px;
    color: #ccc;
    font-weight: 700;
  }
  .nav{
    float: right;
    height: 100%;
    a{
      text-shadow: none;
      color: #ccc;
      font-size: 13px;
      float: none;
      padding: 13px 15px;
      text-decoration: none;
      height: 100%;
    }
  }
}
複製程式碼

完成後的效果

react服務端渲染框架Next.js踩坑(三)

二、axios請求資料

我們使用axios來請求資料,因為axios在客戶端和服務端都能使用。首先安裝axios npm install axios --save 為了方便管理和使用,我們把axios簡單封裝一下。在根目錄建立資料夾utils,utils下新建 axios.js 檔案。

// ~utils/axios.js
import axios from 'axios'

const instance = axios.create({
  baseURL: 'https://cnodejs.org/api/v1',
  timeout: 10000,
})

// 攔截器
instance.interceptors.response.use((response) => {
  return response
}, (error) => {
  return Promise.reject(error)
})
instance.interceptors.request.use((config) => {
  return config
}, (error) => {
  return Promise.reject(error)
})

export default instance
複製程式碼

baseURL 我們填CNode社群的api。
我們請求資料統一在 action 裡請求,現在我們開啟首頁的 action.js 定義一個方法。

// ~pages/home/store/action.js
import axios from '../../../utils/axios'
import * as constants from './constants'

export const changeHomeData = (data) => {
  return {
    type: constants.CHANGE_HOME_DATA,
    data,
  }
}

export const getHomeData = () => {
  return async (dispatch) => {
    const { data } = await axios.get('/topics')
    dispatch(changeHomeData(data))
  }
}
複製程式碼

在Home裡面觸發這個action

// ~pages/home/index.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import style from './style.less'
import { actionCreators } from './store'

class Home extends Component {
  static async getInitialProps({ store }) {
    await store.dispatch(actionCreators.getHomeData());
    return { }
  }

  render() {
    const { changeHome } = this.props
    return (
      <div>
        <div className={style.container}>222</div>
        <button type="button" onClick={() => { changeHome() }}>改變homeData</button>
      </div>
    )
  }
}

const mapState = (state) => {
  return {
    homeData: state.home.homeData,
  }
}

const mapDispatch = (dispatch) => {
  return {
    changeHome() {
      const data = '我改變了'
      dispatch(actionCreators.changeHomeData(data));
    },
  }
}

export default connect(mapState, mapDispatch)(Home)
複製程式碼

在next裡有個非同步方法getInitialProps,當頁面初始化載入時執行,getInitialProps只會載入在服務端。我們需要在這裡使用 dispatch 來觸發獲取資料的action,action獲取完資料觸發 changeHomeData 來改變 homeData。 這樣我們就獲取到了首頁的資料。

三、首頁

寫樣式不是我們這篇的主題,所以具體樣式因為太長我就不貼出來了,有興趣的童鞋去原始碼自取。

// ~pages/home/index.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import style from './style.less'
import { actionCreators } from './store'
import TopicList from './components/TopicList'

class Home extends Component {
  static async getInitialProps({ store }) {
    await store.dispatch(actionCreators.getHomeData());
    return { }
  }

  render() {
    const { homeData } = this.props
    return (
      <div className={`${style.main} flex`}>
        <div className={`${style.content} flex-item`}>
          <div className={style.contentHeader}>
            <a className={`${style.topicTab} ${style.currentTab}`}>全部</a>
            <a className={style.topicTab}>精華</a>
            <a className={style.topicTab}>分享</a>
            <a className={style.topicTab}>問答</a>
          </div>
          <div className={style.innerContent}>
            <div className={style.topicList}>
              {/* 避免介面出錯後頁面顯示不出,這裡做個判斷,有興趣的可以把 empty頁面做得好看點 */}
              { (homeData && homeData.data)
                ? (
                  <TopicList homeData={homeData} />
                ) : (
                  <div className="empty">沒有內容</div>
                )
              }
            </div>
          </div>
        </div>
        <div className={style.sideBar}>
          sideBar
        </div>
      </div>
    )
  }
}

const mapState = (state) => {
  return {
    homeData: state.home.homeData,
  }
}

const mapDispatch = () => {
  return {
  }
}

export default connect(mapState, mapDispatch)(Home)
複製程式碼

這裡我建立了一個元件 <TopicList /> 用來顯示主題列表,在開發過程中建議大家能元件化的就把它做成元件,這樣維護起來比較方便。
在home目錄下建立components資料夾,components裡新建 TopicList.js 檔案。

// ~pages/home/components/TopicList.js
import React, { Fragment } from 'react'
import style from '../style.less'

const getType = (item) => {
  let text = ''
  let classname = style.topiclistTab
  if (item.top) {
    text = '置頂'
    classname = style.putTop
  } else if (item.tab === 'share') {
    text = '分享'
  } else if (item.tab === 'ask') {
    text = '問答'
  }
  if (item.good) {
    text = '精選'
    classname = style.putTop
  }
  return <span className={classname}>{text}</span>
}

const TopicList = ({ homeData }) => {
  return (
    <Fragment>
      {
        homeData.data.map((item) => {
          return (
            <div className={`${style.topicItem} flex flex-align-center`} key={item.id}>
              <a className={style.avatar}>
                <img src={item.author.avatar_url} alt="" />
              </a>
              <span className={style.replayCount}>
                <span className={style.countReplies}>{item.reply_count}</span>
                <span className={style.countSeperator}>/</span>
                <span className={style.countVisit}>{item.visit_count}</span>
              </span>
              {getType(item)}
              <Link href={`/topic/${item.id}`}>
                <a className={`${style.topicTitle} flex-item`}>{item.title}</a>
              </Link>
            </div>
          )
        })
      }
    </Fragment>
  )
}

export default TopicList
複製程式碼

至此CNode簡易版的首頁就完成了

react服務端渲染框架Next.js踩坑(三)

四、總結

這節我們完成了首頁和頭部,裡面的公共樣式reset.css和首頁的樣式太長了所以沒有貼出來,大家可以去原始碼那裡檢視。下一篇是這個系列最後一篇,我會帶大家完成詳情頁和建立其他幾個頁面。其實到現在為止 next.js 的入門基本結束了,下一篇和建立首頁也差不多隻不過是動態路由需要獲取到id引數而已。下面附上到現在為止的目錄結構:

|-- examples
    |-- .babelrc
    |-- .editorconfig
    |-- .eslintrc
    |-- .gitignore
    |-- next.config.js
    |-- package-lock.json
    |-- package.json
    |-- server.js
    |-- components
    |   |-- Header
    |   |   |-- index.js
    |   |   |-- style.less
    |   |-- Layout
    |       |-- index.js
    |-- pages
    |   |-- _app.js
    |   |-- home
    |       |-- index.js
    |       |-- style.less
    |       |-- components
    |       |   |-- TopicList.js
    |       |-- store
    |           |-- action.js
    |           |-- constants.js
    |           |-- index.js
    |           |-- reducer.js
    |-- static
    |   |-- favicon.ico
    |   |-- css
    |   |   |-- reset.css
    |   |-- images
    |       |-- cnodejs_light.svg
    |-- store
    |   |-- index.js
    |   |-- reducer.js
    |-- utils
        |-- axios.js

複製程式碼

相關文章