建站四部曲以前端顯示篇(React+上線)

本系列分爲四篇:

零、前言

本系列爲了總結一下手上的知識,致敬個人2018
本篇的重點在於:用前兩篇的數據使用React搭建一個簡單網站
本篇總結的技術點:
React的組件封裝React實現簡單的懶加載React中的網絡請求搜索功能
React中form表單與接口的對接路由react-router-dom的使用React中文件上傳css


先回顧一下服務端的接口(以ip:192.168.43.60,端口8089爲例)
查詢接口:GET請求

----查詢全部:
http://192.168.43.60:8089/api/android/note
----查詢偏移12條,查詢12條(即12條爲一頁的第2頁):
http://192.168.43.60:8089/api/android/note/12/12
----按區域查詢(A爲Android數據,SB爲SpringBoot數據,Re爲React數據)
http://192.168.43.60:8089/api/android/note/area/A http://192.168.43.60:8089/api/android/note/area/A/12/12
----按部分名稱查詢
http://192.168.43.60:8089/api/android/note/name/材料
http://192.168.43.60:8089/api/android/note/name/材料/2/2
----按類型名稱查詢(類型定義表見第一篇)
http://192.168.43.60:8089/api/android/note/name/ABCS
http://192.168.43.60:8089/api/android/note/name/ABCS/2/2
----按id名稱查
http://192.168.43.60:8089/api/android/note/12前端

添改刪接口

添-POST請求:http://192.168.43.60:8089/api/android/note
添-PUT請求:http://192.168.43.60:8089/api/android/note
刪-DELETE請求:http://192.168.43.60:8089/api/android/note/1node


1、首頁的製做

1.網頁效果(筆記本):已上線,可訪問:www.toly1994.com

手機端用媒體查詢簡單適配了一下react

首頁效果.png

2.示意圖

這裏的數據寫死在了IndexData.js裏,固然也可讓服務端提供數據,方便動態修改
只要格式和IndexData.js裏的json對象保持一致就好了android

首頁.png


3.路由的使用

因爲主頁比較簡單,佈局樣式就不貼了,這裏講一下router的使用ios

3.1:安裝
npm i react-router-dom
複製代碼
3.2:新建一個router.js管理路由

其實也不是很是複雜,一句畫來講就是:
http://http://192.168.43.60/Android能夠訪問到Android組件頁面git

import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'
import React from 'react';
import Index from "./pagers/index/Index";
import Android from "./pagers/Android";
import SpringBoot from "./pagers/SpringBoot";
import ReactJS from "./pagers/ReactJS";
import Note from "./pagers/Note";

export default () => (
    <Router>
        <Switch>
            <Route path={'/index'} component={Index}/>
            <Route path={'/Android'} component={Android}/>
            <Route path={'/SpringBoot'} component={SpringBoot}/>
            <Route path={'/ReactJS'} component={ReactJS}/>
            <Route path={'/Note'} component={Note}/>
            <Route path={'/'} component={Index}/>
        </Switch>
    </Router>
)
複製代碼
3.3:使用
ReactDOM.render(router(), document.getElementById('root'));
複製代碼
3.4:跳轉:
a標籤的href和Link組件的to均可以,若是跳到Android頁,寫上`/Android`就好了
複製代碼

2、單條目的封裝:

單條目的封裝.gif


1.組件狀態:

核心是itemInfo,字段名稱與接口數據保持一致github

this.state = {
    top: "100%",
    itemInfo: {
        type: "數據讀寫",
        name: "1-SI--安卓SQLite基礎使用指南",
        jianshuUrl: "https://www.jianshu.com/p/58076ca06a33",
        imgUrl: "http://192.168.43.60:8089/imgs/android/f593dab6a21907dec2dfed6ffc39b7e4.png",
        createTime: "2018-08-26",
        info: "零、前言 [1]熟悉MySQL的學這個就像會西瓜的人去學吃哈密瓜同樣簡單。[2]若是對MySQL不太熟悉的童鞋,能夠看一下個人這篇:Spring..."
    }
}
複製代碼

2.組件屬性和行爲
//組件屬性
this.props.itemInfo:上層組件傳遞來的數據
this.props.isNew :是否加"新"字  
this.props.css: 暴露樣式修改接口(主要爲了修改寬高)

//組件行爲:
鼠標進入是遮罩層+介紹文字進入+圖片放大
複製代碼

3.分析佈局層級關係

CSS層級關係.png

標籤分級.png


4.標籤的書寫

使用top的變化來讓懸浮時文字移入npm

<div className={"ItemBox"} style={{width: "300px", height: "200px"}}>
    <div className={"box-img-bg"}
         style={{backgroundImage: `url(${this.state.itemInfo.imgUrl})`}}>
    </div>
    <div className="mask-with-text"
         onMouseEnter={() => {
             let itemInfo = this.state.itemInfo;
             this.setState({top: 0, itemInfo})
         }}
         onMouseLeave={() => {
             let itemInfo = this.state.itemInfo;
             itemInfo.text = "";
             this.setState({top: "100%", itemInfo})
         }}>
        <div className="tag">
            <a href="">{this.state.itemInfo.type}</a>
        </div>
        <div className={"text"} style={{
            paddingTop: this.state.top
        }}>
            <a href={this.state.itemInfo.jianshuUrl} target={"_blank"}>
                {this.state.itemInfo.info}
            </a>
        </div>
    </div>
    <div className={"box-info"}>
        <div className={ "new"}>
        </div>
        <div className={"text-info"}>
            <a href={this.state.itemInfo.jianshuUrl} target={"_blank"}>
                {this.state.itemInfo.name}
            </a>
        </div>
    </div>
</div>
複製代碼

5.scss樣式書寫
//使用flex佈局並內容居中
@mixin flexCenter() {
  display: flex;
  justify-content: center;
  align-items: center;
}

//寬高同父控件
@mixin match-parent() {
  width: 100%;
  height: 100%;
}

//文字單行加省略號
@mixin text-single() {
  font-weight: bold;
  text-align: center;
  display: inline-block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

//a標籤的統一處理
@mixin handleA() {
  a {
    color: #fff;
    &:hover {
      color: #4B86FF;
      text-decoration: underline;
    }
  }
}

.ItemBox {
  margin-top: 16px;
  border-radius: 10px;
  position: relative;
  overflow: hidden;
  box-shadow: rgba(214, 214, 214, .8) 1px 1px 2px 2px;

  &:hover {
    .mask-with-text {
      transition: background-color .5s cubic-bezier(0, 0.51, 1, 1);
      background-color: rgba(0, 0, 0, .5);
    }

    .box-img-bg {
      transition: transform .5s cubic-bezier(0, 0.51, 1, 1);
      transform: scale(1.2);
    }
  }

  .box-img-bg {
    border-radius: 10px;
    position: relative;
    background-size: 100%;
    background-repeat: no-repeat;
    @include match-parent;
  }

  .mask-with-text {
    .tag {
      background-image: url("../static/imgs/tag.svg");
      font-size: 10px;
      text-align: center;
      width: 65px;
      height: 65px;
      position: absolute;
      background-size: 100% 100%;
      right: -2px;
      top: -20px;
      @include flexCenter;
      @include handleA;

    }

    border-radius: 10px 0 0 10px;
    position: absolute;
    left: 0;
    top: 0;
    @include match-parent;
    @include flexCenter;
    .text {
      transition: padding-top .6s;
      padding-left: 20px;
      padding-right: 20px;
      @include handleA;
    }
  }

  .box-info {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 25%;
    background-color: rgba(0, 0, 0, .5);
    @include flexCenter;
    .new {
      background-image: url("../static/imgs/new.svg");
      align-self: flex-start;
      width: 30px;
      height: 30px;
      position: absolute;
      left: 0;
      background-size: 30px 30px;
    }
    .text-info {
      @include handleA;
      width: 80%;
      @include text-single()
    }
  }
}

複製代碼

6.靜態界面組件化(屬性對接):
<div className={"ItemBox"} style={this.props.css}>
複製代碼
componentDidMount() {
    this.setState({
        itemInfo: this.props.itemInfo
    })
}
複製代碼

3、獲取數據,填充界面

Page頁的抽取與數據的流入.png


1.數據的獲取(以Android界面爲例)
1.1:添加依賴

這裏使用axios發送請求編程

npm i axios
複製代碼
1.2:獲取數據方法簡單封裝:DataFetcher.js

封裝一下是爲了更符合接口的操做,以便複用

const axios = require('axios');
const BASE_URL = 'http://192.168.43.60:8089';
const API = '/api/android/note/';

export default class DataFetcher {
    static findAll(callback, style = '', offset = 0, num = 10000) {
        let s = BASE_URL + API + style + "/" + offset + "/" + num;
        console.log(s);
        axios.get(s).then(rp => {
            callback(rp.data.data)
        });
    }

    static findAndroid(callback, offset = 0, num = 10000) {
        DataFetcher.findAll(callback, 'area/A', offset, num)
    }

}
複製代碼
1.3:使用方法:

獲取數據.png

DataFetcher.get(data => {
    console.log(data);
}, 'area/A');
複製代碼

2.Pager頁的實現

數據獲取了,就已經萬事具有

2.1.Pager的狀態與屬性:
//Pager的狀態
this.state = {
    data: []
}

//Pager的狀態屬性
this.props.img 背景圖
this.props.type 類型
this.props.sub_title 副標題
this.props.title標題
複製代碼
2.2.數據獲取,更新狀態
componentDidMount() {
    DataFetcher.get(data => {
        this.setState({data})
    }, this.props.type);
}
複製代碼
2.3.根據數據生成視圖
renderBody() {
    return (
        this.state.data.map((i, index) => {
                return (
                    <ItemBox key={index} itemInfo={i}
                             isNew={index < 3}
                             css={{width: "30%", height: "100%"}}>
                    </ItemBox>);
            }
        )
    )
}
複製代碼

2.4.使用

只要改變: pager就能加載不一樣類型的數據

class Android extends Component {
    render() {
        return (
            <div>
                <Pager
                    pager={{
                        img: Logic.loadImg("android.svg"),
                        title: "Android 技術棧",
                        sub_title: "A complete node and summary for Android.",
                        type: "area/A"
                    }}/>
            </div>
        );
    }
}
複製代碼

3.懶加載的實現
3.1:問題所在:

問題所在:請求時是因此數據,遍歷時全部條目都會加載
解決方案:查詢範圍的接口,監聽滾動事件,快到底部時加載更多

圖片所有加載.gif


3.2:滾動監聽:
this.state = {
    dataCount: 9,//默認加載9條
    data: []
}
複製代碼
componentDidMount() {
    let self = this;
    window.onscroll = () => {
        let scrollHeight = document.body.scrollHeight;
        let top = document.documentElement.scrollTop || document.body.scrollTop;
        if (scrollHeight - (top + document.body.clientHeight) < 80) {
            self.state.dataCount += 6;//每次多加載6條
            DataFetcher.get((data) => {
                this.setState({data})
            }, this.props.type, 0, this.state.dataCount);
        }
    };
    DataFetcher.get(data => {
        this.setState({data})
    }, this.props.type, 0, this.state.dataCount);
}
複製代碼

懶加載.gif


4、搜索功能的實現:

搜索功能.gif

折騰了好一會,總算擺弄處理了,期間犯了一個低級失誤,mark一下: 搜索時記得在條目的:componentWillReceiveProps(nextProps)裏更新state


1.查找組件的封裝

很簡單,樣式上面的本身怎麼好看怎麼來吧
回顧一下按部分名稱查詢接口:http://192.168.43.60:8089/api/android/note/name/材料

export default class Searcher extends Component {
    constructor() {
        super();

        this.state = {
            text: ""
        }
    }

    render() {
        return (
            <div className={"pager-search"}>
                <input className="input-search" defaultValue={this.props.searcher.text}
                       onInput={(e) => {
                           this.setState({
                               text: e.target.value
                           });
                       }}>
                </input>
                <img src={Logic.loadImg('search3.svg')} alt=""
                     onClick={() => {
                         this.props.searcher.doOnClick(this.state.text)
                     }}/>
            </div>
        )
    }
}

複製代碼

2.樣式
.pager-search {
  position: absolute;
  right: 0;
  top: 0;
  padding: 10px;
  display: flex;
  justify-content: space-around;
  
  input {
    padding: 6px;
    box-shadow: #EAEAEA 1px 1px 30px 1px;
    width: 60%;
    color: #cccccc;
    border-bottom: transparent;
    border-width: 1px;
    background-color: rgba(195,243,231,.5);
    border-radius: 10px;
    &:focus {
      color: black;
    }
  }

  img {
    width: 50px;
    &:hover {
      transition: transform .5s;
      transform: scale(1.2);
      fill: blue;
    }
  }
}
複製代碼

3.請求方法的提取

這裏定義了一個變量盛放type

let type = '';


componentDidMount() {
    type = this.props.pager.type;//爲type賦值
    //....
}

getData() {//抽取獲取數據函數
    DataFetcher.get(data => {
        this.setState({data})
    }, type, 0, this.state.dataCount);
}
複製代碼

4.搜索框的使用:
<Searcher
    searcher={{
            text: "張風捷特烈是誰?",
            doOnClick: (value) => {
                type = "name/" + value;
                this.getData();
            }
        }
    }/>
複製代碼

5.最重要的一點:ItemBox.js
componentWillReceiveProps(nextProps) {
    this.setState({
        itemInfo: nextProps.itemInfo
    });
}
複製代碼

其實搜索功能自己不難,有後臺接口配合就好了


5、添加操做:

1.使用axios發送post請求,封裝插入方法

使用post請求插入數據.png

static insert(obj) {
    let s = BASE_URL + API;
    let params = new URLSearchParams();
    params.append("type", obj.type);
    params.append("name", obj.name);
    params.append("imgUrl", obj.name);
    params.append("localPath", obj.localPath);
    params.append("jianshuUrl", obj.jianshuUrl);
    params.append("juejinUrl", obj.juejinUrl);
    params.append("createTime", obj.createTime);
    params.append("info", obj.info);
    params.append("area", obj.area);
    axios.post(s, params).then(function (response) {
        alert(response.data.data);
    }).catch(function (error) {
        console.log(error);
    });
}
複製代碼

2.測試插入數據的使用
DataFetcher.insert({
    type: "C",
    name: "hell0",
    localPath: "hell0",
    jianshuUrl: "hell0",
    juejinUrl: "hell0",
    createTime: "2018-12-13",
    info: "hell0",
    area: "A"
});
複製代碼

3.使用axios上傳文件方法封裝
static upload(name,file) {
    let s = BASE_URL + "/api/android/upload";
    let fd = new FormData();
    fd.append(name, file);
    let config = {
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    };
    axios.post(s, fd, config).then(res => {
        console.log(res)
    }).catch(res => {
        console.log(res)
    })
}
複製代碼

4.上傳方法的使用
<form id={"add-form"} onSubmit={this.handleSubmit.bind(this)} method={"post"} name={"add"}
    <label>上傳圖片:<input type="file" name={"file"}/>
    </label>
    <input type="submit" value="提交"/>
</form>
複製代碼
//執行上傳
handleSubmit(event) {
    let input = document.forms['add'].file;
    DataFetcher.upload("file", input.files[0]);
    event.preventDefault();
}
複製代碼

文件上傳成功.png


6、React項目的上線

1.package.json配置homepage
"homepage": "http://toly1994.com"
複製代碼
2.打包
build一下,將生成的build文件加拷貝到服務器
複製代碼
3.運行:確保服務器上有node,而且有serve

沒有serve的話:npm i serve

serve -p 80 -s
複製代碼

上線.png


>那個jQuery隨意操縱dom的時代已經一去不復返了,React的思想很是符合Android  
我常常把React自定義組件和Android自定義控件去比較:  
React組件接收的props就像Android自定義控件中的自定義屬性,而且React靈活不少    
css的佈局就像Android中的佈局,相比而言,css強大不少   
ES6的語法加持,更讓React寫起來符合Javaer的心情,因此React寫起來很舒心
複製代碼

終於打完收工,前端我是打醬油的,不當之處,還請海涵。
下一站,安卓移動端(命屬),敬請期待。


後記:捷文規範

1.本文成長記錄及勘誤表
項目源碼 日期 備註
V0.1 2018-12-13 [建站四部曲以前端顯示篇(React+上線)](www.jianshu.com/p/b0b4776cc…
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
個人github 個人簡書 個人掘金 我的網站
3.聲明

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持


icon_wx_200.png
相關文章
相關標籤/搜索