這節課咱們來完成頭部、首頁的代碼和使用axios進行數據請求。javascript
源碼css
react服務端渲染框架Next.js踩坑(一)
react服務端渲染框架Next.js踩坑(二)
react服務端渲染框架Next.js踩坑(三)
react服務端渲染框架Next.js踩坑(四)java
CNode社區全部頁面的頭部都是同樣的,因此咱們須要把頭部代碼抽出來當成公用組件 Header。
咱們在components目錄下建立文件夾Header,在Header下建立 index.js 和 style.less。node
// ~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。react
// ~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目錄。ios
而後編輯 /pages/_app.js 文件,把 Layout 集成進去。git
// ~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的樣式。github
// ~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%;
}
}
}
複製代碼
完成後的效果web
咱們使用axios來請求數據,由於axios在客戶端和服務端都能使用。首先安裝axios npm install axios --save
爲了方便管理和使用,咱們把axios簡單封裝一下。在根目錄建立文件夾utils,utils下新建 axios.js 文件。npm
// ~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 Link from 'next/link'
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
複製代碼