胖蔡叨叨叨
你听我说

JS 中的DOM与BOM

胖蔡阅读(105)

JS的组成

  • ECMAScript
    描述JS的语法与基本对象,ECMAScript是一个标准,JS只是它的一个实现
  • DOM(文档对象模型)
    处理网页内容的方法和接口,是 W3C 的标准,所有浏览器公共遵守的标准
  • BOM(浏览器对象模型)
    与浏览器交互的方法和接口,各个浏览器厂商根据 DOM在各自浏览器上的实现,表现为不同浏览器定义有差别,实现方式不同.window 是 BOM 对象,而非 js 对象;javacsript是通过访问BOM(Browser Object Model)对象来访问、控制、修改客户端(浏览器)

A区:(浏览器的标签页,地址栏,搜索栏,书签栏,窗口放大还原关闭按钮,菜单栏等等)
B区:(document加载时的状态栏,显示http状态码等)
C区: (滚动条scroll bar)
E区: (浏览器的右键菜单)
F区: html显示区域

DOM与DOM结构

DOM

Document Object Model(文档对象模型),就是把「文档」当做一个「对象」来看待,相当于把html页面结构解析成一个对象,提供一个接口API,让你去操作所有的节点。在 DOM 中,文档中的各个组件(component),可以通过 object.attribute 这种形式来访问。一个 DOM 会有一个根对象,这个对象通常就是 document。DOM通常包括HTML DOM 和 XML DOM。


根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:整个文档是一个文档节点:

  • 每个 HTML 元素是元素节点
  • HTML 元素内的文本是文本节点
  • 每个 HTML 属性是属性节点
  • 注释是注释节点
<html>
<head>
<title>文档标题</title>
</head>
<body>
<a href=“”>我的链接</a>
<!--这是一段注释-->
<h1>我的标题</h1>
</body>
</html>

<!--xml-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:orientation="vertical"
              android:background="@color/white"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <com.worfu.base.widget.HeadBarView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:title="现金明细"/>
    <com.google.android.material.tabs.TabLayout
            android:id="@+id/mTabLay"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            app:tabSelectedTextColor="@color/colorTabSelect"
            app:tabIndicatorFullWidth="false"
            app:tabTextAppearance="@style/TabLayoutTextStyle"
            app:tabBackground="@android:color/transparent"
            app:tabRippleColor="@android:color/transparent" >
    </com.google.android.material.tabs.TabLayout>
    <androidx.viewpager.widget.ViewPager
            android:id="@+id/mViewPager"
            style="@style/NoScrollBar"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
    </androidx.viewpager.widget.ViewPager>
</LinearLayout>

DOM节点树

DOM 方法是我们可以在节点(HTML 元素)上执行的动作,可通过 JavaScript (以及其他编程语言)对 HTML DOM 进行访问。所有 HTML 元素被定义为对象,而编程接口则是对象方法和对象属性。方法是您能够执行的动作(比如添加或修改元素)。属性是您能够获取或设置的值(比如节点的名称或内容)
一些常用的 HTML DOM 方法:

  • getElementById(id) – 获取带有指定 id 的节点(元素)
  • appendChild(node) – 插入新的子节点(元素)
  • removeChild(node) – 删除子节点(元素)

一些常用的 HTML DOM 属性:

  • innerHTML – 节点(元素)的文本值
  • parentNode – 节点(元素)的父节点
  • childNodes – 节点(元素)的子节点
  • attributes – 节点(元素)的属性节点

BOM(浏览器对象模型)

BOM(Browser Object Model) 是指浏览器对象模型。由于最初JavaScript就是设计在浏览器中执行的脚本语言,所以BOM是浏览器提供给JavaScript操作自身的接口。BOM的核心对象是window对象,window对象表示一个浏览器的一个页面窗口对象。同时window对象也是ECMAScript定义的Global对象,即网页中定义的全局属性或者全局方法最终都是定义到该对象上,同时提供JS的Global的方法如parseInt方法。

BOM对象

  • Window 对象:表示浏览器打开的窗口,包括获取焦点、改变滚动条、设置定时器等等。
  • Navigator 对象:包含浏览器信息。如:获取浏览器名称、版本信息、操作系统平台信息等等。
  • Screen 对象:包含屏幕信息。如:获取屏幕高度、宽度等等。
  • History 对象:可对当前页的浏览历史进行操作,如:前进、后退等。
  • Location 对象:可对当前页面的URL进行操作,如:导航到新的页面、获取URL信息等。

window对象是BOM(浏览器对象模型)的核心。window对象既是通过JS访问浏览器窗口的接口,也是ECMAScript规定的global对象, 由于window对象是js中的顶级对象,所有定义在全局作用域中的变量、函数都会变成window对象的属性和方法,在调用的时候可以省略window。

Promise入门

方玉新阅读(116)

预备知识

函数对象与实例对象

  • 函数对象: 将函数作为对象使用时, 简称为函数对象
  • 实例对象: new 函数产生的对象, 简称为对象


回调函数的分类


1、同步回调
理解: 立即执行, 完全执行完了才结束, 不会放入回调队列中
例子: 数组遍历相关的回调函数 / Promise的excutor函数
2、异步回调
理解: 不会立即执行, 会放入回调队列中将来执行
例子: 定时器回调 / ajax回调 / Promise的成功|失败的回调

错误


1. 错误对象
        name属性: 错误类型
        message属性: 错误相关信息
2. 错误的类型
        EvalError: 已在 eval() 函数中发生的错误 (更新版本的 JavaScript 不会抛出任何 EvalError, SyntaxError)
        ReferenceError: 引用的变量不存在
        TypeError: 数据类型不正确的错误
        RangeError: 数据值不在其所允许的范围内
        SyntaxError: 语法错误
URIError:在 encodeURI() 中已发生的错误
3. 错误处理
        捕获错误: try … catch
        抛出错误: throw error

Promise 的理解和使用

  • Promise 是什么
  • Promise 的理解

抽象表达:Promise 是 JS 中进行异步编程的新的解决方案
具体表达:
语法上:Promise 是一个构造函数
功能上:Promise 对象用来封装一个异步操作并可以获取其结果

  • Promise 的状态改变

pending 变为 resolved
pending 变为 rejected
只有这两种,且一个 promise 对象只能改变一次。无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value,而失败的一般称为 reason。

  • Promise 的基本流程
  • Promise 的基本使用
new Promise((resolve, reject) => {
  resolve(1)
  //  reject(2)
}).then(value => {
  console.log('value', value)
}, reason => {
  console.log('reason1', reason)
}).catch(reason => {
  console.log('reason2', reason)
})

为什么要用 Promise

  • 指定回调函数的方式更加灵活

旧的:必须在启动异步任务前指定
promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)

  • 支持链式调用,可以解决回调地狱问题

什么是回调地狱?
回调函数嵌套调用,外部回调函数异步执行的结果是其内部嵌套的回调函数执行的条件
回调地狱的缺点?
不便于阅读 / 不便于异常处理
解决方案
promise 链式调用
终极解决方案
async/await

如何使用 Promise

Promise中的API

  • Promise 构造函数:Promise(excutor) {}

excutor 函数:同步执行 (resolve, reject) => {}
resolve 函数:内部定义成功时调用的函数 resove(value)
reject 函数:内部定义失败时调用的函数 reject(reason)
说明:excutor 是执行器,会在 Promise 内部立即同步回调,异步操作 resolve/reject 就在 excutor 中执行

  • Promise.prototype.then 方法:p.then(onResolved, onRejected)

1)onResolved 函数:成功的回调函数 (value) => {}
2)onRejected 函数:失败的回调函数 (reason) => {}
说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调,返回一个新的 promise 对象

  • Promise.prototype.catch 方法:p.catch(onRejected)

onRejected 函数:失败的回调函数 (reason) => {}
说明:then() 的语法糖,相当于 then(undefined, onRejected)

  • Promise.resolve 方法:Promise.resolve(value)

value:将被 Promise 对象解析的参数,也可以是一个成功或失败的 Promise 对象
返回:返回一个带着给定值解析过的 Promise 对象,如果参数本身就是一个 Promise 对象,则直接返回这个 Promise 对象。

  • Promise.reject 方法:Promise.resolve(reason)

reason:失败的原因
说明:返回一个失败的 promise 对象

  • Promise.all 方法:Promise.all(iterable)

iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String
说明:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败。

  • Promise.race方法:Promise.race(iterable)

iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String
说明:返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态。

Promise 的几个关键问题

如何改变 promise 的状态

  • resolve(value):如果当前是 pending 就会变为 resolved
  • reject(reason):如果当前是 pending 就会变为 rejected
  • 抛出异常:如果当前是 pending 就会变为 rejected

一个 promise 指定多个成功/失败回调函数,都会调用吗?

当 promise 改变为对应状态时都会调用。

改变 promise 状态和指定回调函数谁先谁后?

  • 都有可能,常规是先指定回调再改变状态,但也可以先改状态再指定回调
  • 如何先改状态再指定回调?
    1. 在执行器中直接调用 resolve()/reject()
    2. 延迟更长时间才调用 then()
  • 什么时候才能得到数据?
    1. 如果先指定的回调,那当状态发生改变时,回调函数就会调用得到数据
    2. 如果先改变的状态,那当指定回调时,回调函数就会调用得到数据

promise.then() 返回的新 promise 的结果状态由什么决定?

  • 简单表达:由 then() 指定的回调函数执行的结果决定
  • 详细表达:
    ​ ① 如果抛出异常,新 promise 变为 rejected,reason 为抛出的异常
    ​ ② 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值。
    ​ ③ 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果。

promise 如何串联多个操作任务?

  • promise 的 then() 返回一个新的 promise,可以并成 then() 的链式调用。
  • 通过 then 的链式调用串联多个同步/异步任务。

Promise 异常穿透(传透)?

  • 当使用 promise 的 then 链式调用时,可以在最后指定失败的回调。
  • 前面任何操作出了异常,都会传到最后失败的回调中处理。

中断 promise 链?

当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数
办法:在回调函数中返回一个 pending 状态的 promise 对象

nodejs基于express搭建接口服务

xuao阅读(381)

编写react时做了前后端分离的构建模式,那么就需要后端提供接口数据,决定使用nodejs编写简单的后端服务。本文主要是介绍了如何通过nodejs完成一个后端api的开发、调用、和线上部署的流程。希望能对大家有所作用。

本文转载自XUAO,版权归作者所有,本站仅做转载传播,点击可查看原文

nodejs server 编写

创建一个工程目录,我就取名叫 blog-server
cmd到这个工程目录下安装相关依赖:

npm install express
npm install hotnode
npm install mysql
npm install cors
npm install body-parser

新建一个index.js引入需要使用的组件包,这个index.js就作为即将可以使用的服务入口。

const cors = require('cors');
const bodyParser = require('body-parser');
const express = require('express');
const mysql = require('mysql');
const app = express();

app.use(cors()); // 解决跨域
app.use(bodyParser.json()); // 解析参数,json请求
app.use(bodyParser.urlencoded({ extended: true }));// 表单请求

开启端口监听,当浏览器或者其他工具访问http://localhost:8888/时,会被本服务监听到

app.listen(8888, () => console.log('server running at http://localhost:8888/'));

监听get请求,并返回一段html标签,监听地址为基地址

app.get('/', (req, res) => {
    res.send('<div style="color:red">error</div>')
})

get带参请求,并返回一段json数据,数据库查询的使用稍后再说

// 博客列表
app.get('/article-list', (req, res) => {

    // page=1&pageSize=10&classifyId=2
    var params = req.query

    var pageSize = 15;
    if (params.pageSize) { // 分页大小
        pageSize = params.pageSize;
    }

    var page = 1;
    if (params.page) { // 当前页数
        page = params.page;
    }

    var classifyId = '';
    if (params.classifyId) { // 分类id
        classifyId = '&& type=' + params.classifyId;
    }

    const data = {
        total: 0,
        blog: []
    }

    conn.query('select count(1) as total from ‘表名’ WHERE state=1 ' + classifyId + ';', (e1, r1) => {
        data.total = r1[0].total;

        if (e1) {
            // 此处返回一段json数据
            res.json(new Result({ code: 1, msg: "fail", data: e1 }))
            return
        }

        conn.query('SELECT * FROM ‘表名’ WHERE state=1 ' + classifyId + ' LIMIT ' + (page - 1) * pageSize + ',' + pageSize + ';', (e2, r) => {

            if (e2) {
                res.json(new Result({ code: 1, msg: "fail", data: e2 }))
                return
            }

            r.forEach(element => {
                element.content = element.content.substring(0, 100) + "..."
                data.blog.push(element)
            });

            res.json(new Result({ data: data }))
        })
    })
})

post带参请求,

// 发起留言
app.post('/leave-message', (req, res, next) => {

    console.log(req.body)

    var params = req.body;

    // 如果没有留言则不需要处理
    if (!params.message) {
        res.json(new Result({ code: 1, msg: "fail", data: params }))
        return
    }

    const post = {
        name: params.name,
        message: params.message,
        time: new Date().getTime()
    }

    conn.query('INSERT INTO ‘表名’ SET ?', post, (err, results) => {
        if (err) {
            console.log('err', err)
            res.json(new Result({ code: 1, msg: "fail", data: err }))
            return
        }
        res.json(new Result({ data: post }))

    })
})

封装的一个统一返回的最外层数据格式

function Result({ code = 0, msg = "success", data = null }) {
    this.code = code;
    this.msg = msg;
    this.data = data;
}

数据库的建立与连接

后端数据离不开数据库,nodejs连接远程数据库特别简单,先设置基础配置项,详情见注释

const option = {
    host: 'localhost', // 数据库所在地址,如果需要访问远程数据库,可以查询远程数据库配置,此处只要填写地址
    user: 'root', // 数据库用户名
    password: '123456', // 数据库访问密码,这里必填,如果数据库没有创建密码,到了这里就需要给root用户创建一个密码
    port: '3306', // 数据库开放端口
    database: 'blog', // 数据库名
    connectTimeout: 5000, // 连接超时时间
    multipleStatements: false // 不允许多sql语句执行,防止sql注入攻击,默认false
}

建立数据库连接,此时已经由第三方组件库帮我们把复杂的内容处理完了,用就可以了。

const conn = mysql.createConnection(option);

数据库查询语句

  conn.query('SELECT * FROM ‘表名’;', (e1, r) => {

        if (e1) {
            res.json(new Result({ code: 1, msg: "fail", data: e1 }))
            return
        }
        res.json(new Result({ data: r }))
    })

数据库插入,更多用法查阅 mysql 的具体使用文档

 const post = { // 需要插入数据的参数
        name: params.name,
        message: params.message,
        time: new Date().getTime()
    }

    conn.query('INSERT INTO ‘表名’ SET ?', post, (err, results) => {
        if (err) {
            console.log('err', err)
            res.json(new Result({ code: 1, msg: "fail", data: err }))
            return
        }
        res.json(new Result({ data: post }))

    })

重要配置

如果工程根目录下没有生成 package.json文件就新建一个并添加配置 dependencies 为导包版本所以此文件应该是自动生成的,如果生成此文件就修改配置,如下:

{
  "main":"index.js",
  "scripts": {
    "start": "hotnode index.js"
  },
  "dependencies": {
    "body-parser": "^1.19.0",
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "hotnode": "^0.0.8",
    "multer": "^1.4.2",
    "mysql": "^2.18.1"
  }
}

运行

至此,一个简单的nodejs实现的后端服务基本完成,在控制台中输入启动命令:

npm start

最后在浏览器中访问 http://localhost:8888/ 即有数据返回,post请求方式可以使用postman测试。

备注

hotnode这个扩展库存在的目的是热更新,即每次修改代码后不需要重新启动

React 自定义ListView组件

胖蔡阅读(146)

ListView组件实现

通过适配器的实现,适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。这样就可以让ListView如同Android中的ListView一样动态设置Item布局、数据格式,Item类型等。

/* eslint no-dupe-keys: 0 */
import React, { Component } from "react";

class ListView extends Component{

  constructor(props) {
    super(props);
    // console.log("get props:",this.props)
    this.state = {
      data: props.data?props.data:[],
    };
  }

  componentDidMount() {
    //组件首次加载时
    console.log("get componentDidMount props:",this.props)
  };

  componentWillReceiveProps(nextProps){
    if(this.props.data != nextProps.data){
      this.setState({
        data:nextProps.data?nextProps.data:[],
      })
    }
  };

  /**
   * 
   * @param {*子布局数据格式} item 
   * @param {*子布局位置} index 
   */
  getItem(item,index){
    let itemLayout = this.props.adapter(item,index,{onClick:this.onItemClick.bind(this,item,index)})
    return itemLayout
  };

  onItemClick(item,index){
    console.log("-------------pre click log--------------")
    return this.props.OnItemClick(item,index)
  };

  render() {
    return (
      <div className="list" >
        {
          {
         !!this.state.data?(this.state.data.map((item,index) => {
           return this.getItem(item,index)
         })):null
        }
        }
      </div>   
    );
  }
}

export default ListView;

ListView组件调用

ListView可以在任意页面调用使用,只需在listView中指定adapter适配内容和点击操作接口即可,代码调用如下:

import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import {
  NavBar,
  Icon,
  Carousel,
  WingBlank,
  NoticeBar,
  WhiteSpace,
} from "antd-mobile";
import { StickyContainer, Sticky } from "react-sticky";
import ListView from "./listview";
import * as homeActions from "../redux/reduces/home";
import { Toast } from "../../node_modules/antd-mobile/lib/index";
import axios from "axios";


axios.defaults.timeout = 100000;
axios.defaults.baseURL = "http://test.mediastack.cn/";

/**
 * http request 拦截器
 */
axios.interceptors.request.use(
  (config) => {
    config.data = JSON.stringify(config.data);
    config.headers = {
      "Content-Type": "application/json",
    };
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

@connect(
  (state) => ({ home: state.home }),
  (dispatch) => bindActionCreators(homeActions, dispatch)
)
class Home extends Component {
  state = {
    data: ["1", "2", "3"],
    imgHeight: 176,
  };

  constructor(props) {
    super(props);

    setTimeout(() => {
      this.setState({
        data: [
          "AiyWuByWklrrUDlFignR",
          "TekJlZRVCjLFexlOCuWn",
          "IJOtIlfsYdTyaDTRVrLI",
        ],
      });
    }, 100);
  }

  componentDidMount() {

    axios.get('/article/home/index').then(
      (res) => {
        this.setState({
          list: res.data.data,
        });
        console.log("get article response:", res);
      },
      (error) => {
        console.log("get response failed:", error);
      }
    );
  }

  handleBrowserChange = () => {
    const { history, changeRoute } = this.props;
    changeRoute();
    history.push("/docs");
  };

  /**
   * 推荐内容适配器
   * @param {*} item
   * @param {*} index
   */
  RecommentAdapter(item, index,props) {
    return (
      <div
        {...props}
        className="item"
        style={{
          height: 80,
          backgroundColor: "#fff",
          textAlign: "left",
          padding: "0 15px 0",
          display: "flex",
          fontSize: 20,
          flexFlow: "column nowrap",
          justifyContent: "center",
          marginBottom: 10,
        }}
        key={index}
      >
        <label
          className="title"
          style={{
            minWidth: 0,
            overflow: "hidden",
            textOverflow: "ellipsis",
            whiteSpace: "nowrap",
          }}
        >
          {item.title}
        </label>
        <div style={{
          fontSize:12,
          marginTop:12,
          color:'#888888'
        }}>
        <span style={{
          color:'#111111',
          fontWeight:'500',
        }}>{item.category}</span> |
        <span> {item.author}</span> |
        <span> 阅读量:{item.views}</span> |
        <span> {item.date}</span>
        </div>

      </div>
    );
  };


  onItemClick(item,index){
    Toast.show("你点击的第"+index+"个item.",Toast.SHORT)
  };

  render() {
    return (
      <div className="home">
        <StickyContainer>
          <Sticky>
            {({
              style,
              isSticky,
              wasSticky,
              distanceFromTop,
              distanceFromBottom,
              calculatedHeight,
            }) => (
              <NavBar
                style={{
                  ...style,
                  zIndex: 3,
                  padding: "2px 0",
                }}
                mode="light"
                icon={<Icon type="left" />}
                onLeftClick={() => console.log("onLeftClick")}
                rightContent={[
                  <Icon
                    key="0"
                    type="search"
                    style={{ marginRight: "16px" }}
                  />,
                  <Icon key="1" type="ellipsis" />,
                ]}
              >
                胖蔡杂谈
              </NavBar>
            )}
          </Sticky>
          {/* Sticky 为悬浮框 */}
          <WingBlank>
            <WhiteSpace size="lg" />
            <Carousel
              autoplay={false}
              infinite
              beforeChange={(from, to) =>
                console.log(`slide from ${from} to ${to}`)
              }
              afterChange={(index) => console.log("slide to", index)}
            >
              {this.state.data.map((val) => (
                <a
                  key={val}
                  href="http://www.alipay.com"
                  style={{
                    display: "inline-block",
                    width: "100%",
                    height: this.state.imgHeight,
                  }}
                >
                  <img
                    src={`https://zos.alipayobjects.com/rmsportal/${val}.png`}
                    alt=""
                    style={{
                      width: "100%",
                      verticalAlign: "top",
                    }}
                    onLoad={() => {
                      // fire window resize event to change height
                      window.dispatchEvent(new Event("resize"));
                      this.setState({
                        imgHeight: "auto",
                      });
                    }}
                  />
                </a>
              ))}
            </Carousel>
            <WhiteSpace size="lg" />
            <NoticeBar
              marqueeProps={{ loop: true, style: { padding: "0 7.5px" } }}
            >
              通知:北京继续暂停出租车顺风车出京运营
              合肥明日继续新一波消费券发放,预计发放4000万元
            </NoticeBar>
            <WhiteSpace size="lg" />
            <ListView data={this.state.list} 
                  adapter={this.RecommentAdapter} 
                  OnItemClick={this.onItemClick}/>
          </WingBlank>
        </StickyContainer>
      </div>
    );
  }
}

export default Home;

adapter 返回单个Item的jsx,可通过React组件或者方法返回均可。

React应用中封装axios

胖蔡阅读(185)

Axios简介

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

特性

  • 支持node端和浏览器端
  • 支持拦截器等高级配置
  • 使用Promise管理异步,告别传统callback方式
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

安装

  1. yarn 安装
$ yarn add axios
  1. npm 安装
npm install axios -D

3.bower 安装

$ bower install axios

简单使用

aixos 可直接通过cdn加载实用,如下示例:

<html>
<head>
<title>Axios的使用 </title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.js"></script>
</head>
<body>
   <input type="button" onclick="getList()" value="点击获取"/>
   <div id="content"> </div>

   <script type="text/javascript">

      function getList(){
             axios.request({
                 url:'/article/home/index',
                 method:'get',
                 baseURL:'http://test.mediastack.cn/'

            }).then(
               res => {
                 console.log("get res:",res);
                 var str=JSON.stringify(res);
                 document.getElementById("content").innerHTML = str;

              },error => {
                 console.log("get request failed:",error);
                 document.getElementById("content").innerHTML = error;
              }
            );
      }

   </script>
</body>
</html>

react中封装axios

react中可以将axios封装成一个文件,通过控制操作,可以实现错误、逻辑、和验证统一处理,降低代码的冗余度和可读性。

请求封装

/**
 * 网络请求配置
 */
import axios from "axios";

axios.defaults.timeout = 100000;
axios.defaults.baseURL = "http://test.mediastack.cn/";

/**
 * http request 拦截器
 */
axios.interceptors.request.use(
  (config) => {
    config.data = JSON.stringify(config.data);
    config.headers = {
      "Content-Type": "application/json",
    };
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

/**
 * http response 拦截器
 */
axios.interceptors.response.use(
  (response) => {
    if (response.data.errCode === 2) {
      console.log("过期");
    }
    return response;
  },
  (error) => {
    console.log("请求出错:", error);
  }
);

/**
 * 封装get方法
 * @param url  请求url
 * @param params  请求参数
 * @returns {Promise}
 */
export function get(url, params = {}) {
  return new Promise((resolve, reject) => {
    axios.get(url, {
        params: params,
      }).then((response) => {
        landing(url, params, response.data);
        resolve(response.data);
      })
      .catch((error) => {
        reject(error);
      });
  });
}

/**
 * 封装post请求
 * @param url
 * @param data
 * @returns {Promise}
 */

export function post(url, data) {
  return new Promise((resolve, reject) => {
    axios.post(url, data).then(
      (response) => {
        //关闭进度条
        resolve(response.data);
      },
      (err) => {
        reject(err);
      }
    );
  });
}

/**
 * 封装patch请求
 * @param url
 * @param data
 * @returns {Promise}
 */
export function patch(url, data = {}) {
  return new Promise((resolve, reject) => {
    axios.patch(url, data).then(
      (response) => {
        resolve(response.data);
      },
      (err) => {
        msag(err);
        reject(err);
      }
    );
  });
}

/**
 * 封装put请求
 * @param url
 * @param data
 * @returns {Promise}
 */

export function put(url, data = {}) {
  return new Promise((resolve, reject) => {
    axios.put(url, data).then(
      (response) => {
        resolve(response.data);
      },
      (err) => {
        msag(err);
        reject(err);
      }
    );
  });
}

//统一接口处理,返回数据
export default function (fecth, url, param) {
  let _data = "";
  return new Promise((resolve, reject) => {
    switch (fecth) {
      case "get":
        console.log("begin a get request,and url:", url);
        get(url, param)
          .then(function (response) {
            resolve(response);
          })
          .catch(function (error) {
            console.log("get request GET failed.", error);
            reject(error);
          });
        break;
      case "post":
        post(url, param)
          .then(function (response) {
            resolve(response);
          })
          .catch(function (error) {
            console.log("get request POST failed.", error);
            reject(error);
          });
        break;
      default:
        break;
    }
  });
}

//失败提示
function msag(err) {
  if (err && err.response) {
    switch (err.response.status) {
      case 400:
        alert(err.response.data.error.details);
        break;
      case 401:
        alert("未授权,请登录");
        break;

      case 403:
        alert("拒绝访问");
        break;

      case 404:
        alert("请求地址出错");
        break;

      case 408:
        alert("请求超时");
        break;

      case 500:
        alert("服务器内部错误");
        break;

      case 501:
        alert("服务未实现");
        break;

      case 502:
        alert("网关错误");
        break;

      case 503:
        alert("服务不可用");
        break;

      case 504:
        alert("网关超时");
        break;

      case 505:
        alert("HTTP版本不受支持");
        break;
      default:
    }
  }
}

/**
 * 查看返回的数据
 * @param url
 * @param params
 * @param data
 */
function landing(url, params, data) {
  if (data.code === -1) {
  }
}

如上,可通过过对axios请求的拦截实现添加公共请求头,token 验证等操作。

请求隔离

import http from '../utils/http';



/**
 * 获取首页列表
 */
function getArticleList(){
  return new Promise((resolve, reject) => {
    http("get",'/article/home/index').then(res => {
      resolve (res);
    },error => {
      console.log("网络异常~",error);
      reject(error)
    })
  }) 
}

export {
   getArticleList
}

react 组件调用如下:

import React, { Component } from "react";
import { getArticleList } from "~/api/blog";

class Home extends Component {
    constructor(props) {
        super(props);
    } 

    componentDidMount() {
       getArticleList().then(
          (res) => {
              console.log("get article response:", res);
          },
         (error) => {
              console.log("get response failed!");
          }
       );
    }

 ......
}


export default Home;

这样,可以做到尽量让数据请求可以页面逻辑互不干扰,也方便后期统一维护和修改。

Js 中export 和import的使用

胖蔡阅读(10028)

概述

export 和 import 是ES6中模块化的两个较为重要的模块,ES6 的模块自动开启严格模式,模块可以导入各种类型的变量、对象、函数、字符串、类等,每个模块都有自己的上下文,每个模块内声明的变量都是局部变量,不会污染全局作用域。每个模块只加载一次,若其他文件需要加载,则直接从内存中读取。

基本用法

  • export default外,导出的函数什么和类声明都必须要有名称
  • export可出现在模块的任何位置,但必需处于模块顶层
  • import命令会提升到整个模块的头部,首先执行。
  • 不仅能导出声明还能导出引用(例如函数)
  1. export导出
//test1.js
import axios from 'axios'

export function get({url,params = {}}) {
    return new Promise((resolve,reject) => {
        axios.get(url,{
            params:params
        }).then( response =>{
            landing(url, params, response.data);
            resolve(response.data);
        }).catch(error =>{
             reject(error)
        })
    })

}

//test2.js
import { get } from '../utils/http';
//import { get as http } from '../utils/http';



/**
 * 获取首页列表
 */
function  getArticleList(){
  return new Promise((resolve, reject) => {
    get('article/home/index').then(res => {
      resolve (res);
    },error => {
      console.log("网络异常~",error);
      reject(error)
    })
  }) 
}


export导入方法、变量可通过 **import {}**指定参数导入,或通过as更改导入名,export方式导出对象、方法、引用等不可直接指定对象、方法,需要通过大括号”{}”来指定属性、变量名。

  1. export default导出
//test1.js
//统一接口处理,返回数据
export default function HttpData (fecth,url, parm) {
	let _data = "";
	return new Promise((resolve, reject) => {
		switch (fecth){
			case "get":
				get(url, parm).then(function (response) {
					resolve (response);
				}).catch(function (error) {
					console.log("get request failed.",error);
				});
				break;
			case "post":
				post(url, parm).then(function (response) {
					resolve (response);
				}).catch(function (error) {});
				break;
			default:
				break;
		}

	});
}


//test2.js
//test2.js
import http from '../utils/http';



/**
 * 获取首页列表
 */
function  getArticleList(){
  return new Promise((resolve, reject) => {
    http('get','article/home/index').then(res => {
      resolve (res);
    },error => {
      console.log("网络异常~",error);
      reject(error)
    })
  }) 
}

export default导出对象有如下特点:

  • 可不必须指定函数名、类名等
  • 在一个文件或模块中,export、import 可以有多个,export default 仅有一个
  • export default 向外暴露的成员,可以使用任意变量来接收
  • export default 向外暴露的成员,import时不需要用大括号嵌套,也不能用大括号嵌套
  1. import * as

import * as 的用法是将导入的模块或文件中所有的 export组合成一个对象,其中export default放在对象的default属性下访问:

//test.js
export const  name ="胖蔡";
export const sex = "男";
let age = 123;
export default  age;

//test1.js
import * as person from  "../test/test"

console.log("get person info:",person);

// log info
get person info: 
{name: "胖蔡", sex: "男", default: 123, __esModule: true}
default: 123
name: "胖蔡"
sex: "男"
__esModule: true
__proto__: Object

React babel配置

xuao阅读(375)

用react开发企业微信的web项目,会发现在pc端的企业微信内置浏览器显示空白,即react项目没有运行,只执行到第一个index.html文件。初步发现发生这种情况的原因是pc端企业微信内置浏览器不支持es6语法,而react使用的是es6语法,知道原因后再去修改就很容易了。那是不是只要把es6语法转成其支持的es5语法即可以了呢?答案是是的。

babel安装

详细内容查看babel中文网 安装依赖:

  • yarn add @babel/cli
  • yarn add @babel/parser
  • yarn add @babel/plugin-proposal-class-properties
  • yarn add @babel/plugin-transform-react-jsx
  • yarn add @babel/polyfill
  • yarn add @babel/preset-flow
  • yarn add @babel/preset-react
  • yarn add @babel/core
  • yarn add @babel/preset-env
  • yarn add babel-loader

由于项目比较赶,至于为什么要安装这么多的依赖我自己也还没有弄清楚,最终依赖了这么多完全是根据安装时控制台的报错信息一步一步安装出来的。

babel配置

工程根目录下新建文件 babel.config.json,并添加内容:

{
    "presets":[
        "@babel/preset-react",
        "@babel/preset-env"
    ],
    "plugins": ["@babel/plugin-proposal-class-properties"]
}

配置比网上找的其他文章都要简单,我不知道是不是我使用了蚂蚁样式库的原因,由于主要在移动端使用,所以用了 ant design mobile 的库,package.json的配置如下,可供参考:

{
...
 "dependencies": {
    "@babel/cli": "^7.10.5",
    "@babel/parser": "^7.11.0",
    "@babel/plugin-proposal-class-properties": "^7.10.4",
    "@babel/plugin-transform-react-jsx": "^7.10.4",
    "@babel/polyfill": "^7.10.4",
    "@babel/preset-flow": "^7.10.4",
    "@babel/preset-react": "^7.10.4",
    "antd-mobile": "^2.3.3",
    "babel-plugin-import": "^1.13.0",
    "react": "^16.13.1",
    "react-app-rewired": "^2.1.6",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1",
    ...
  },
 "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test --env=jsdom",
    "eject": "react-scripts eject"
  },
 "devDependencies": {
    "@babel/core": "^7.11.0",
    "@babel/preset-env": "^7.11.0",
    "babel-loader": "^8.1.0"
  }
...
}

使用

此时执行 npm run build命令,将打包后的全部文件放到服务器访问目录就可以在pc端企业微信内置浏览器访问项目,且正常运行。

计划

每次修改都需要打包后检查修改的结果,我喜欢按帧编程,那么这样就特别麻烦,所以我想有没有实时转码的方式呢?

useState 函数式状态管理

胖蔡阅读(10428)

React 有两种方式可以实现组件的定义,一种是通过函数实现,一种是通过class类实现。一般的通过class类实现的组件我们可以看到足够多的生命周期、状态state可供操作,而函数式组件则没有生命周期和状态值state。对就性能而言,由于函数组件需要初始化组件,而函数组件是直接通过return 返回JSX,因此函数组件的性能要优于类组件。为了性能考虑,一般的我们推荐使用函数组件来实现组件的定义。

出于操作考虑,React自从16.8.0版本开始支持Hook功能,让我们可以通过hook方式在函数组件中实现state、生命周期的监听。这里,我们介绍hook的一种用法useState,通过useState来实现状态值的操作。操作之前,需要注意几点:

  • 只能在React函数中调用 即是必须在组件函数中调用,不可在一般的js中使用useState等hook实现。
  • 在函数返回部分顶部调用 不可在jsx代码部分使用hook实现,须得如同js的变量定义方式使用useState等hook实现。

Class状态管理

在没有hook之前,我们通常使用class组件的方式来实现状态的管理:

import React, { Component } from 'react';

export default class extends Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date(),counter:0};
  }

  

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
         <button onClick={() => this.state((state, props) => ({
              counter: state.counter + 1}))}>
        Click me
      </button>
      </div>
    );
  }

} 

当使用useState时

当使用hook在函数组件中实现状态管理,对我们而言操作是变得更简单了。

import React, { useState } from 'react';

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

如何使用useState

通过上面的示例,我们基本可以简单的总结useState的使用方法。

  • 定义state 我们可以通过useState来定义一个state对象:
//useState格式
const [state, setState] = useState(initialState);

useState通过传入一个初始的对象值initialState,从而返回一个状态值state(初始状态值为initialState),和一个更新状态的更新函数setState

  • 使用更新函数 使用上述返回的更新函数来更新状态值:
setState(state+1);

如何在React工程中配置less支持?

胖蔡阅读(341)

Less是css的一个变种,css的一个高级体现,与其类似的还有sass,都是一种编译型样式语言。这样的语言对于程序员而言,具有更高的可读性,理解起来也容易很多,层次性较好,这在这几年市场的选择也可以明显看出。如此,这就需要我们对当前的项目进行less依赖,从而让我们的项目支持less。

依赖下载

$yarn add less less-loader #或者
$npm install less less-loader

无论是less亦或者是sass都是通过对文件的编译生成新的css的过程。因此,如果我们想要库能实现less支持,需要先加载基础的依赖库。

修改配置文件

依赖安装好了后,我们还需要修改我们的less配置,以实现webpack在打包时同时编译打包less文件,想要修改配置需得先将配置文件找到,webpack脚手架为安全考虑,默认是将config文件隐藏。因此,我们需要先暴露配置文件,然后再修改配置。

  • 获取配置文件

获取配置文件可通过如下脚本触发。

$yarn eject

  • 修改config/webpack.config.js

我们需要在webpack.config.js中添加css的模块规则,配置类似其中的css配置方式:

const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
//我们添加的less配置
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
......
{
                            test: lessRegex,
                            exclude: lessModuleRegex,
                            use: getStyleLoaders({
                                    importLoaders: 3,
                                    sourceMap: isEnvProduction && shouldUseSourceMap,
                                },
                                'less-loader'
                            ),
                            // Don't consider CSS imports dead code even if the
                            // containing package claims to have no side effects.
                            // Remove this when webpack adds a warning or an error for this.
                            // See https://github.com/webpack/webpack/issues/6571
                            sideEffects: true,
                        }, {
                            test: lessModuleRegex,
                            use: getStyleLoaders({
                                    importLoaders: 3,
                                    sourceMap: isEnvProduction && shouldUseSourceMap,
                                    modules: {
                                        getLocalIdent: getCSSModuleLocalIdent,
                                    },
                                },
                                'less-loader'
                            ),
                        },

使用

如此就完成了less在react中的配置,实现了react中支持less文件。之后我们就可以创建less并在js中导入less文件来生效样式。

/**index.less **/

body{
  font-size:14px;
  height:100%;
  .main {
     max-width:1200px;
     height:100%;
  }
}

React 上下文Context的使用

胖蔡阅读(10149)

依照3W学习原则的模式出发来思考Context的使用问题,在学会如何使用Context之前,首先我们需要来思考一下几个问题:

  •  Context是什么?
  •  为什么需要用Context?
  •  怎么样使用Context?

使用之前较为清晰的了解使用研究对象更加有助于我们的认知和加深我们对Context的理解程度。

Context是什么?

Context(上下文)一直在各种语言、平台之中占据较为重要的地位,它是整个程序的链接者,一直贯穿应用的整个生命周期。React中的Context也类似,Context旨在为React复杂嵌套的各个组件提供一个生命周期内的统一属性访问对象,从而避免我们出现当出现复杂嵌套结构的组件需要一层层通过属性传递值得问题。

其设计之初的构想就是为了提供一个组件树形结构内的一个数据共享的容器。

为什么需要用Context?

为何要用Context,前文也有所介绍,其主要原因还是由于不使用Context无法完成某些业务场景或者是实现起来很难。我们可以通过两张图来了解下在是否使用Context场景下,其对应的属性值传递的过程。来帮助我们了解为什么Context非用不可。

  • 不使用Context
    38c97f991cc3b41

传统方式不使用Context,数据的传递如上图属于嵌套关系,只能通过有父子包含关系的组件一层层进行传递,数据传递的层级随着结构的增加而成倍增加。

  • 使用Context
49ee2ce83f6e77a

使用Context方式进行数据的共享,各个树状组件均可通过统一的Consumer统一访问全局的Consumer共享数据。

怎么样使用Context?

Context的使用主要分为创建、插入、访问三个流程,通过创建context对象提供共享组件,将Provider指定嵌套至需要使用共享数据的顶层结构,然后各后代组件通过context访问共享数据(变量、常量、方法等)。接下来,通过一个封装的登录组件来学习使用Context。

创建一个context

首先,我们需要创建一个待使用的context对象放在一边,等待使用的使用导入,我们可以将其封装成一个单独的js文件并将其export:

// 创建文件LoginContext.js
import { createContext } from 'react';

const LoginContext = createContext({});
export default LoginContext;

Provider包装一个组件

为了降低程序各个功能块的耦合性,防止Context共享数据的滥用和误用。一般的,我们可以将单独的一个模块(需要使用共享数据),进行一层封装,封装成一个单独的组件使用,如全局的多语言切换就可以在顶级组件App内封装。

//创建一个LoginFrom.js
import { Tabs, Form } from 'antd';
import React, { useState } from 'react';
import useMergeValue from 'use-merge-value';
import classNames from 'classnames';
import LoginContext from './LoginContext';
import LoginItem from './LoginItem';
import LoginSubmit from './LoginSubmit';
import LoginTab from './LoginTab';
import styles from './index.less';

const Login = props => {
  const { className } = props;
  const [tabs, setTabs] = useState([]);
  const [active, setActive] = useState();
  const [type, setType] = useMergeValue('', {
    value: props.activeKey,
    onChange: props.onTabChange,
  });
  const TabChildren = [];
  const otherChildren = [];
  React.Children.forEach(props.children, child => {
    if (!child) {
      return;
    }

    if (child.type.typeName === 'LoginTab') {
      TabChildren.push(child);
    } else {
      otherChildren.push(child);
    }
  });
  return (
    <LoginContext.Provider
      value={{
        tabUtil: {
          addTab: id => {
            setTabs([...tabs, id]);
          },
          removeTab: id => {
            setTabs(tabs.filter(currentId => currentId !== id));
          },
        },
        updateActive: activeItem => {
          if (active[type]) {
            active[type].push(activeItem);
          } else {
            active[type] = [activeItem];
          }

          setActive(active);
        },
      }}
    >
      <div className={classNames(className, styles.login)}>
        <Form
          form={props.from}
          onFinish={values => {
            if (props.onSubmit) {
              props.onSubmit(values);
            }
          }}
        >
          {tabs.length ? (
            <React.Fragment>
              <Tabs
                animated={false}
                className={styles.tabs}
                activeKey={type}
                onChange={activeKey => {
                  setType(activeKey);
                }}
              >
                {TabChildren}
              </Tabs>
              {otherChildren}
            </React.Fragment>
          ) : (
            props.children
          )}
        </Form>
      </div>
    </LoginContext.Provider>
  );
};

Login.Tab = LoginTab;
Login.Submit = LoginSubmit;
Login.UserName = LoginItem.UserName;
Login.Password = LoginItem.Password;
Login.Mobile = LoginItem.Mobile;
Login.Captcha = LoginItem.Captcha;
export default Login;

从上面的代码我们可以看到LoginContext.Provider 提供了一个共享对象:

 <LoginContext.Provider
      value={{name:'title',value:'Provider'}}
    >
    ......

    </LoginContext.Provider>
      

一般的我们通过设置value值来指定共享数据的内容。

调用共享数据

通过上面的创建和定义,接下来我们就可以在Provider包含组件内进行共享数据的调用获取,而不需要一层层的去传递了。

//创建文件LoginTab.js
import React, { useEffect } from 'react';
import { Tabs } from 'antd';
import LoginContext from './LoginContext'; // 创建的共享context

const { TabPane } = Tabs;

const generateId = (() => {
  let i = 0;
  return (prefix = '') => {
    i += 1;
    return `${prefix}${i}`;
  };
})();

const LoginTab = props => {
  useEffect(() => {
    const uniqueId = generateId('login-tab-');
    const { tabUtil } = props;

    if (tabUtil) {
      tabUtil.addTab(uniqueId);
    }
  }, []);
  const { children } = props;
  return <TabPane {...props}>{props.active && children}</TabPane>;
};

const WrapContext = props => (
  <LoginContext.Consumer>
    {value => <LoginTab tabUtil={value.tabUtil} {...props} />}
  </LoginContext.Consumer>
); // 标志位 用来判断是不是自定义组件

WrapContext.typeName = 'LoginTab';
export default WrapContext;

上面的代码可以看出,我们可以通过LoginContext.Consumer包裹器包裹获取共享数据Context,其获取数据格式:

 <LoginContext.Consumer>
    {value => (/**这里可以渲染对应的jsx代码,value就是Provider中提供的value**/)}
  </LoginContext.Consumer>