這節課我們來完成頭部、首頁的程式碼和使用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%;
}
}
}
複製程式碼
完成後的效果
二、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簡易版的首頁就完成了
四、總結
這節我們完成了首頁和頭部,裡面的公共樣式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
複製程式碼