美文网首页Android技术知识Android开发Android进阶之路
建站四部曲之前端显示篇(React+上线)

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

作者: e4e52c116681 | 来源:发表于2018-12-13 16:58 被阅读17次
    本系列分为四篇:

    零、前言

    本系列为了总结一下手上的知识,致敬我的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.1 2018-12-13 [建站四部曲之前端显示篇(React+上线)](https://www.jianshu.com/p/b0b4776cc08e
    2.更多关于我
    笔名 QQ 微信 爱好
    张风捷特烈 1981462002 zdl1994328 语言
    我的github 我的简书 我的掘金 个人网站
    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持


    icon_wx_200.png

    相关文章

      网友评论

        本文标题:建站四部曲之前端显示篇(React+上线)

        本文链接:https://www.haomeiwen.com/subject/tmybhqtx.html