建站四部曲之前端显示篇(React+上线)

react

本系列分为四篇:

  • 建站四部曲之后端接口篇(SpringBoot+上线)
  • 建站四部曲之Python数据爬虫篇(selenium)
  • 建站四部曲之前端显示篇(React+上线)
  • 建站四部曲之移动端篇(Android)


零、前言

本系列为了总结一下手上的知识,致敬我的2018

本篇的重点在于:用前两篇的数据使用React搭建一个简单网站

本篇总结的技术点:
React的组件封装React实现简单的懒加载React中的网络请求搜索功能
React中form表单与接口的对接路由react-router-dom的使用React中文件上传


先回顾一下服务端的接口(以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/1


一、首页的制作

1.网页效果(笔记本):已上线,可访问:http://www.toly1994.com

手机端用媒体查询简单适配了一下

首页效果.png

2.示意图

这里的数据写死在了IndexData.js里,当然也可以让服务端提供数据,方便动态修改

只要格式和IndexData.js里的json对象保持一致就行了

首页.png


3.路由的使用

由于主页比较简单,布局样式就不贴了,这里讲一下router的使用

3.1:安装

npm i react-router-dom

3.2:新建一个router.js管理路由

其实也不是非常复杂,一句画来说就是:
http://http://192.168.43.60/Android可以访问到Android组件页面

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`就行了


二、单条目的封装:

单条目的封装.gif


1.组件状态:

核心是itemInfo,字段名称与接口数据保持一致

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的变化来让悬浮时文字移入

<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

})

}


三、获取数据,填充界面

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


四、搜索功能的实现:

搜索功能.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

});

}

其实搜索功能本身不难,有后台接口配合就行了


五、添加操作:

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


六、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.12018-12-13[建站四部曲之前端显示篇(React+上线)](https://www.jianshu.com/p/b0b4776cc08e

2.更多关于我

笔名QQ微信爱好
张风捷特烈1981462002zdl1994328语言
我的github我的简书我的掘金个人网站

3.声明

1----本文由张风捷特烈原创,转载请注明

2----欢迎广大编程爱好者共同交流

3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正

4----看到这里,我在此感谢你的喜欢与支持


icon_wx_200.png

以上是 建站四部曲之前端显示篇(React+上线) 的全部内容, 来源链接: utcz.com/z/381841.html

回到顶部