react基礎學習和react服務端渲染框架next.js踩坑

react、next

說明

React做爲Facebook 內部開發 Instagram 的項目中,是一個用來構建用戶界面的優秀 JS 庫,於 2013 年 5 月開源。做爲前端的三大框架之一,React的應用能夠說是很是的普遍。這裏講一個react服務端渲染的框架-next.js踩坑過程。php

技術棧

react、next.js、ant design、axios

大綱

按照如下思路來寫:css

react、next

react基本語法

react基本語法參照react文檔,這裏發放一個連接https://doc.react-china.org/html

如下是React重要的部分

  • JSX – 容許咱們編寫相似HTML的語法,轉換爲JavaScript對象;
  • 虛擬DOM – 實際DOM的JavaScript表示;
  • React.Component – 建立新組件的方式;
  • render(方法) – 渲染組件;
  • ReactDOM.render – 用於將模板轉爲 HTML 語言,並插入指定的 DOM 節點;
  • state – 組件的內部數據存儲(對象),類型理解vue的data;
  • constructor(this.state) – 創建組件初始 state(狀態) 的方式;
  • setState – 一種輔助方法,用於更新組件的 state(狀態) 並從新渲染,相似微信小程序;
  • props – 從父組件傳遞給子組件的數據;
  • propTypes – 容許您控制傳遞給子組件的某些 props(屬性) 的存在或類型;
  • defaultProps – 容許您爲組件設置默認 props(屬性) ;
  • className - 節點的class,css標記
  • style jsx樣式控制

jsx語法

直接寫在 JavaScript 語言之中,不加任何引號,它容許 HTML 與 JavaScript 的混寫。前端

//demo-1.js
import React from 'react';
class Demo1 extends React.Component{
  render(){
    const lists = ['我是誰','我來自哪裏','我要到哪兒去'];
    return(
      <div className="list">
        <ul>
          {
            lists.map((list,index)=>{
              return(
                <li key={index}>{list}</li>
              )
            })
          }  
        </ul>
      </div>
    )
  }
}

export default Demo1;

上面代碼體現了 JSX 的基本語法規則:遇到 HTML 標籤(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析,其中注意一點react沒有vue中v-for遍歷,須要本身寫遍歷。上面代碼的運行結果以下:vue

頁面渲染效果

組件及組件傳值

//demo-2.js
import React from 'react';

class MyComponent extends React.Component{
  render(){
    return (
      <h1>{this.props.name}</h1>
    )
  }
}

class Demo2 extends React.Component{
  render(){
    const lists = ['我是誰','我來自哪裏','我要到哪兒去'];
    return(
      <div className="list">
        <ul>
          {
            lists.map((list,index)=>{
              return(
                <li key={index}>
                  <MyComponent name={list}></MyComponent>
                </li>
              )
            })
          }  
        </ul>
        
      </div>
    )
  }
}

export default Demo2;

React 容許將代碼封裝成組件(component),而後像插入普通 HTML 標籤同樣,在網頁中插入這個組件。以上代碼中MyComponent即爲組件,須要注意的一點組件類的第一個字母必須大寫,不然會報錯,組件類只能包含一個頂層標籤,不然也會報錯。vue經過子組件中的props來傳遞數據,而React則是用this.props.name來傳遞。下圖爲渲染頁面效果:node

渲染頁面效果

React之ref(獲取真實的DOM節點)

  • 同vue的ref做用同樣,組件並非真實的DOM節點,而是存在於內存之中的一種數據結構,叫作虛擬 DOM。
  • 只有當它插入文檔之後,纔會變成真實的 DOM 。根據 React的設計,全部的DOM 變更,都先在虛擬 DOM上發生,而後再將實際發生變更的部分,反映在真實 DOM上,這種算法叫作 DOM diff ,它能夠極大提升網頁的性能表現。
  • 有時須要從組件獲取真實 DOM 的節點,這時就要用到 ref 屬性。
//dem0-3.
import React from 'react';

class Demo3 extends React.Component{
  handleClick(){
    this.refs.myTextInput.focus();
  }
  render(){
    return(
      <div>
        <input type="text" ref="myTextInput" />
        <br/>
        <input type="button" value="Focus the text input" onClick={this.handleClick.bind(this)} />
      </div>
    )
  }
}

export default Demo3;

在這裏須要注意的是:react

  • React的事件中若是不用剪頭函數,那就要用bind來綁定this,不然須要在constructor構造函數裏調用this.handleClick = this.handleClick.bind(this),這裏建議用onClick={this.handleClick.bind(this)}。
  • 因爲 this.refs.[refName] 屬性獲取的是真實 DOM ,因此必須等到虛擬 DOM 插入文檔之後,才能使用這個屬性,不然會報錯。上面代碼中,經過爲組件指定 Click 事件的回調函數,確保了只有等到真實 DOM 發生 Click 事件以後,纔會讀取 this.refs.[refName] 屬性。

demo3

React之this.state以及點擊事件

React中的state就至關於vue裏的data數據存儲,而小程序的this.setData就和React的this.setState相似。webpack

//demo-4.js
import React from 'react';

class Demo4 extends React.Component{
  // constructor(props){
  //   super(props)
  //   this.state = {
  //     liked:false
  //   }
  // }
  state = {
    liked: false
  }
  handleClick(){
    this.setState({liked: !this.state.liked});
  }
  render(){
    let text = this.state.liked ? '喜歡' : '不喜歡';
    return(
      <div>
        <p onClick={this.handleClick.bind(this)}>
          你 {text} 這個點擊.
        </p>
      </div>
    )
  }
}

export default Demo4;

上面代碼是一個 LikeButton 組件,它的 constructor 方法(或者直接state聲明)用於定義初始狀態,也就是一個對象,這個對象能夠經過 this.state 屬性讀取。當用戶點擊組件,致使狀態變化,this.setState 方法就修改狀態值,每次修改之後,自動調用 this.render 方法,再次渲染組件。ios

因爲 this.props 和 this.state 都用於描述組件的特性,可能會產生混淆。一個簡單的區分方法是,this.props 表示那些一旦定義,就再也不改變的特性,而 this.state 是會隨着用戶互動而產生變化的特性。git

demo4.gif

利用input的onChange事件實現簡單的雙向綁定

vue裏面v-model一鍵實現的事情React沒有,咱們能夠利用input組件的onChange事件來簡單實現它,直接上代碼。

//Demo5.js
import React from 'react';

class Demo5 extends React.Component{
  // constructor(props){
  //   super(props)
  //   this.state = {
  //     text:''
  //   }
  // }
  state = {
    text: ''
  }
  handleChange(e){
    this.setState(
      {text: e.target.value}
    );
  }
  render(){
    return(
      <div>
        <p>
          你輸入了{this.state.text}
        </p>
        <input type="text" placeholder="請輸入..." onChange={this.handleChange.bind(this)}/>
      </div>
    )
  }
}

export default Demo5;

demo5.gif

組件的生命週期

組件的生命週期.png

一個React組件的生命週期分爲三個部分:掛載期(Mounting)、存在更新期(Updating)和銷燬時(Unmounting)。

  • Mounting:已插入真實 DOM

    當一個組件實例被建立而且插入到DOM中,如下鉤子將被調用 constructor()
    繼承react的props,和設置state的初始化。

constructor(props) {
  super(props); //不能缺乏
  this.state = {
    color: props.initialColor
  };
}
  • Updating:當組件的props和state發生改變時,從新渲染頁面是調用的。
  • Unmounting:組件從DOM中移動時調用的。
React 爲每一個狀態都提供了兩種處理函數,will 函數在進入狀態以前調用,
did 函數在進入狀態以後調用,三種狀態共計五種處理函數。
  • componentWillMount()
  • componentDidMount()
  • componentWillUpdate(object nextProps, object nextState)
  • componentDidUpdate(object prevProps, object prevState)
  • componentWillUnmount()

事件

  • onClick-點擊事件
  • onSubmit-表單提交事件,能夠配合antdesign的表單校驗
  • onChange-input框數據變化,可實現數據的雙向綁定(react裏沒有vue的v-modal)

style jsx

咱們能夠經過普通樣式(className)和行內樣式(LineStyle)控制React組件的樣式:

  • 使用className設置樣式(與CSS的選擇器相同)
import React from 'react';
// import styles from '../../static/css/demo.css'

class Demo6 extends React.Component{
  // constructor(props){
  //   super(props)
  //   this.state = {
  //     text:''
  //   }
  // }
  state = {
    text: ''
  }
  handleChange(e){
    this.setState(
      {text: e.target.value}
    );
  }
  render(){
    return(
      <div>
        <p className="style">
          你輸入了{this.state.text}
        </p>
        <input className="input" type="text" placeholder="請輸入..." onChange={this.handleChange.bind(this)}/>
        <style jsx>
          {
            ` .style {
                background: #dcdcdc;
                font-size: 20px;
                height:40px;
                line-height:40px;
                padding:0 2%;
              }
              .input{
                width:96%;
                height:40px;
                padding:0 2%;
              }
            `
          }
        </style>
      </div>
    )
  }
}

export default Demo6;

demo6

爲何要用next

Next.js是一個基於React的一個服務端渲染簡約框架。它使用React語法,能夠很好的實現代碼的模塊化,有利於代碼的開發和維護。

  • 默認服務端渲染模式,以文件系統爲基礎的客戶端路由,相似nuxt.js;

  • 與nuxt.js相同,pages文件目錄即路由;

  • 以webpack的熱替換爲基礎的開發環境(npm run dev);

next建立的項目目錄結構

next建立的項目目錄結構.png

next路由

  • 默認從 pages 目錄下取頁面進行渲染返回給前端展現,路由可使用Link(無刷新頁面)或者a標籤(刷新頁面)
//pages/router.js
import Link from 'next/link';

<Link prefetch href='/about?id=11'>
    <a >Link</a>
</Link>

<a href="/about?id=11">Link</a>
  • 接收參數:getInitialProps方法context參數或者{props.query.id},同時在getInitialProps 裏面獲取數據,服務端渲染會提早執行這個方法獲取數據渲染到模板,這裏用到axios庫,先後端均可以使用
//pages/test.js http://localhost:3000/test
import React from 'react';
import MyHeader from '../components/MyHeader';
import axios from '../lib/axios';
import { bmobConfigDevdev, bmobConfig } from '../lib/config';
import urls from '../lib/urls';

import { Table, Form, Icon, Input, Button } from 'antd';
const FormItem = Form.Item;

const config1 = {
  headers: {
    'X-Bmob-Application-Id': bmobConfig.applicationId,
    'X-Bmob-REST-API-Key': bmobConfig.restApiKey,
    'Content-Type': 'application/json'
  }
};

const config2 = {
  headers: {
    'X-Bmob-Application-Id': bmobConfigDevdev.applicationId,
    'X-Bmob-REST-API-Key': bmobConfigDevdev.restApiKey,
    'Content-Type': 'application/json'
  }
};

const myStyle = {
  body: {
    width: '1200px',
    margin: '10px auto'
  }
}

const columns = [{
  title: 'id',
  dataIndex: 'objectId',
  key: 'objectId',
}, {
  title: '姓名',
  dataIndex: 'uname',
  key: 'uname',
}, {
  title: '電話',
  dataIndex: 'uphone',
  key: 'uphone',
}, {
  title: '地址',
  dataIndex: 'address',
  key: 'address',
}];

const dataSource = [{
  key: '1',
  name: '胡彥斌',
  age: 32,
  address: '西湖區湖底公園1號'
}, {
  key: '2',
  name: '胡彥祖',
  age: 42,
  address: '西湖區湖底公園1號'
}];

//封裝form組件
const MyForm = (props) => (
  <Form layout="inline" onSubmit={this.handleSubmit}>
    <FormItem>
      <Button
        type="primary"
        htmlType="submit"
      >
        增長數據
      </Button>
    </FormItem>
  </Form>
)

//聲明類
class addDeleteSelectUpdate extends React.Component {

  render() {
    return (
      <MyHeader title="用戶列表">
        <div style={myStyle.body}>
          <p className="about">傳遞的參數爲:{this.props.query}</p>
          <p className="about">增長數據</p>
          <MyForm ></MyForm>
          <p className="about">獲取數據{this.props.dataToString}</p>
          <Table dataSource={this.props.data} columns={columns} size="small" />
          <div className="">
            <p className="about">遍歷</p>
            <ul>
              {
                this.props.data.map(els => {
                  return (
                    <li key={els.objectId}>
                      {els.address}
                    </li>
                  )
                })
              }
            </ul>
          </div>
          <style jsx>
            {`
              .about {color:#666;padding:10px}
            `}
          </style>
        </div>
      </MyHeader>
    )
  }
}

/**
 * @description 獲取初始數據
 */
const isServer = typeof window === 'undefined'

addDeleteSelectUpdate.getInitialProps = async function (context) {
  console.log(context.query);
  const res = await axios.get(urls.userList, config1)
  console.log(res.data);
  var query = context.query;
  return {
    data: res.data.results,
    dataToString: JSON.stringify(res.data.results),
    query: JSON.stringify(context.query)
  }
}

export default addDeleteSelectUpdate;

數據請求-axios配置

Axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。

axios具備如下幾個特色:

  • 既能夠在服務端使用又能夠在瀏覽器端使用;
  • 支持 Promise API(async await,解決地獄回調);
  • 攔截請求和響應(401跳轉登陸等操做);
  • 轉換請求數據和響應數據;
  • 自動轉換 JSON 數據。

如下爲axios配置代碼:

//lib/axios.js
import axios from 'axios';
import NProgress from 'nprogress';

axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.withCredentials = true;
axios.defaults.timeout = 5000;

//請求以前
axios.interceptors.request.use((config)=>{
  if(process.broswer) NProgress.start()//加一個loading
  return config;
});

//
axios.interceptors.response.use(
  response => {
    // console.log('-------axiosResponse---------')
    if(process.broswer) NProgress.done()
    return response;
  },
  error => {
    // console.log('-------axiosError---------:');
    // console.log(error.response.status==401)
    if(process.broswer) NProgress.done()
    if(error.response&&error.response.status==401){
      window.location.href="/login";//登陸失效跳轉
    }
    return Promise.reject(error);
  }
);

export default axios;

使用時在頁面引入axios配置文件axios.js,具體的axios配置能夠參考link-axios文檔

//pages/login.js
import axios from '../lib/axios';
...

axios.post(urls.login,qs.stringify(obj))
.then(res=>{
    console.log(res);
    Router.push('/contract/list');
})
.catch(error=>{
    console.log(error);
})
...

數據請求-開發環境代理

先後端分離之後,前端最多見的一個問題就是跨域,以前的om項目基於nuxt能夠配置proxy,在next.js裏須要用到express和http-proxy-middleware在服務端(node)端作一層轉發,代碼配置以下:

//server.js
var express =require('express');
var next = require('next');

const devProxy = {
  '/api': {
    target: 'http://39.107.58.75:9092/',// 目標服務器 host
    pathRewrite: {'^/api': '/'}, // 重寫請求,好比咱們源訪問的是api/login,那麼請求會被解析爲/www/login
    changeOrigin: true,// 默認false,是否須要改變原始主機頭爲目標URL
  },
  '/jt':{
    target: 'https://api.dededemo.com/plus/weapp.php?action=index&domain=http://23jt.net&skin=weapp&ver=3.0&page=1',// 目標服務器 host
    // pathRewrite: {'^/api': '/'}, // 重寫請求,好比咱們源訪問的是api/login,那麼請求會被解析爲/www/login
    changeOrigin: true,// 默認false,是否須要改變原始主機頭爲目標URL
  }
}

const port = parseInt(process.env.PORT, 10) || 3000
const env = process.env.NODE_ENV
const dev = env !== 'production'
const app = next({
  dir: '.', // base directory where everything is, could move to src later
  dev
})

const handle = app.getRequestHandler()

let server
app
  .prepare()
  .then(() => {
    server = express()
      
    // Set up the proxy.
    if (dev && devProxy) {
      const proxyMiddleware = require('http-proxy-middleware')
      Object.keys(devProxy).forEach(function (context) {
        server.use(proxyMiddleware(context, devProxy[context]))
      })
    }

    // Default catch-all handler to allow Next.js to handle all other routes
    server.all('*', (req, res) => {
        req.headers['Content-Type'] = 'application/x-www-form-urlencoded';
        req.headers['X-Requested-With'] = 'XMLHttpRequest';
        handle(req, res);
        // console.log(req.body);
      }
    )

    server.listen(port, err => {
      if (err) {
        throw err
      }
      console.log(`> Ready on port ${port} [${env}]`)
    })
  })
  .catch(err => {
    console.log('An error occurred, unable to start the server')
    console.log(err)
  })

配置完server.js後還須要在packge.json中配置啓動腳本:

"devdev": "cross-env NODE_ENV=development PORT=9527 node server.js",

注意上線後須要在ngnix裏配置反向代理,就不存在跨域的問題。

在next.js裏使用ant design

  • babel配置
//.babelrc
{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    [
      "module-resolver", {
      "root": ["."],
      "alias": {
        "styles": "./styles"
      },
      "cwd": "babelrc"
    }],
    ["import", { "libraryName": "antd" }]
  ],
  "ignore": []
}
  • 引用ant design組件,須要用到ant design什麼組件按照本身須要引用便可,各個組件使用方法參考ant design的link-官方文檔
import {Form, Icon,Input, Button, Checkbox, message} from 'antd';

部署一個next.js項目

Next.js 項目的部署,須要一個 Node.js的服務器。
在開發環境服務器的入口文件就使用上文中提到的 server.js,在 server.js 裏添加了針對部署環境的選擇,代碼以下

const dev = process.env.NODE_ENV !== 'production'

爲了區分部署環境,咱們須要在 package.json 中修改 script 屬性以下:

"scripts": {
    "dev": "next",
    "devdev": "cross-env NODE_ENV=development PORT=9527 node server.js",
    "build": "next build",
    "start": "next start",
    "generate": "next build && next export",
    "export-h":"next export -h"
  },

其中,build 命令是用於打包項目,start 命令是用於生產環境部署,devdev 命令是用於本地開發,PORT爲啓動端口。

注意的幾個點

  • 增長第三方npm包必須加上--save關鍵字,會pakage.json文件里加入依賴包;
//如增長js-md5庫
$ npm install js-md5 --save
  • 組件名稱必須以大寫字母開頭,如MyHeader
  • React的事件中若是不用剪頭函數,那就要用bind來綁定this,不然須要在constructor構造函數裏調用this.handleClick = this.handleClick.bind(this),這裏建議用onClick={this.handleClick.bind(this)}。

總結

只要熟悉React開發,上手一個Next項目很容易,Next 讓前端項目開發效率更高。

參考資料

  • ant design官方文檔:https://ant.design/docs/react/introduce-cn
  • ant design腳手架市場:http://scaffold.ant.design/#/?tags=antd&tags=react
  • ant design使用小結:https://www.cnblogs.com/wx1993/p/6511389.html
  • 用Next.js快速上手React服務器渲染 https://segmentfault.com/p/1210000010368182/read
  • React 服務端渲染框架 Next.js 基於 Gank api 實戰 https://orangexc.xyz/2017/10/19/Nextjs-gank/
  • 基於 React 的通用框架 Next.js:服務端 React(axios)
    https://www.zcfy.cc/article/react-universal-with-next-js-server-side-react-2158.html
  • 使用next.js完成從開發到部署 https://juejin.im/post/5b08078b51882538ad3f163d
  • react學習線路圖 https://juejin.im/entry/5b49af6d6fb9a04fe25ec224
  • React使用Next.js做服務器端渲染 https://blog.csdn.net/xcg132566/article/details/78857017
  • css modules 用法 http://www.ruanyifeng.com/blog/2016/06/css_modules.html

更多

更多前端技術請訪問個人博客:https://hurely.github.io

相關文章
相關標籤/搜索