微信小程序实现双向数据绑定

input绑定data随后监听输入事件获取event对象从中拿到value

//index.js
//获取应用实例
const app = getApp()

Page({
  data: {
    inputValue:"双向数据绑定",
  },
  inputEdit(event){
    this.setData({
      inputValue:event.detail.value
    })
  }
})
<!--index.wxml-->
<view class="container">
  <!-- 双向数据绑定 -->
  <input type="text" value="{{inputValue}}" bindinput="inputEdit"/>
  <text>{{inputValue}}</text>
</view>

但是这样去做虽然能实现效果但是变量不好维护,我们可以使用data-*=“”绑定与data。相同的值实现动态绑定。

<!--index.wxml-->
<view class="container">
  <!-- 使用data-*双向数据绑定 -->
  <input type="text" value="{{inputValue}}" bindinput="inputEdit" data-key="inputValue"/>
  <text>{{inputValue}}</text>
</view>
//index.js
//获取应用实例
const app = getApp()

Page({
  data: {
    inputValue:"双向数据绑定",
    },
  inputEdit(event){
    this.setData({
      [event.target.dataset["key"]]:event.detail.value
    })
  }
})

搞定

不愧是我

撕碎那个幻梦,去破坏理想的天空。
刚看完一本关于洗脑的影片,有感而发。
当年梁山好汉们每一个都身怀绝技为什么心甘情愿听从宋江一个文官的话?当然讲义气是一部分,更重要的是宋江是一个洗脑的高手!

先告诉其余人,你们虽然都有很大的本事,但是说到底咱们都一样,都是强盗,罪人!咱们这辈子大口喝酒大块吃肉,那咱们的后人那?他们会被别人戳脊梁骨!,我们每个人都身怀绝技为什么不替天行道?

什么是天?自然是皇帝,宋江是希望被招安的,随后就带领梁山好汉们招安去了,不说招安后的结果如何,单说能把这100多人搞得死心塌地的安排宋江的设计走,这里宋江就应用了洗脑一个很重要的手段。

这里先进行打压说你们都是强盗!罪人!打压完成后得让他们兴奋起来啊。随后就是鼓励,说我们做的是好事,替天行道,劫富济贫。一冷一热之间就得找其中的合理性,咱们受招安,从此不是江湖散仙,顺应天道。

经过这三步 恐吓->鼓励->最后指条明路(目的)。宋江把108将整的服服帖帖。

而当今社会洗脑不仅没有减少反而各个领域都有,就拿我举例,我个人认为我定力还算可以,但是有一阵理财产品找上我基本就是买,然后没过两天就赔了什么p2p跑路,买了垃圾币,很多,其实就是被洗脑了。

你也许认为你不会被洗脑,自认为见多识广对这些“江湖骗术”了如指掌,现在的洗脑术跟过去不同,是一门结合了心理学,社会学,行为学等等,他设定的每一步都是按照人心的思路走的。

就比如看一些公安题材的电视剧,一般布控完成抓捕犯人都是凌晨一两点钟,很多人会认为这个时间是人最疲惫的时候,容易抓捕且目标反抗能力弱。这是其一大家都认为的,其实这里还有后续就是“连夜审讯”这在司法解释里叫“捕获冲击”,人在这个时候被审讯心理防线早已崩塌,就比较容易获得口供。

算卦的也是如此,行话叫“扣瓜子”说你将来可能会出一些问题,但是你可以在我这里买一些什么东西趋吉避凶,保证你平平安安顺风顺水。这里大家就应该发现了,欸这不就是宋江那套,先恐吓,然后给希望,最后买它“保命”产品?

你可能会说就这?我当然不会上当,但是这里我说的只是一些简单的场景,真正的算卦大师的洗脑术可比我说的要厉害多了,这里就分享一下我面试的时候面试官给我的洗脑套路。看看是不是符合以上几点。

我记得我面试的是一个静态页面设计开发岗,去的是一家做知识产权的公司,因为刚步入社会,虽然我w能力不止于此但是为了先适应公司环境我先从一些简单的项目入手随后在进开发组也没啥毛病,毕竟我稳的一逼。面试的时候面试官看了我的简历看到我会Vue然后就问了我一个Vue很刁钻的问题"说说solt"是什么吧,我第一反应是“卧槽高手啊”,因为我看过很多面试的视频很少有上来就问这种问题的,当然我也是有备而来,就说了solt是vue6个内置组件的其中一个,作用就是“留空”插入你想要的任何内容。我寻思我这应该是满分的回答了,我不仅说出了solt的作用还炫了下我对内置组件的掌握的程度,然而面试官没吃我这套,随后又问了我solt什么场景下应用的多,我一开始对这个问题是蒙住的随后突然想到,脱口而出,在封装组件的时候。虽然我知道我的答案是正确的,但是我意识到了他后面可能要问封装组件的一些问题,我已经开始慌了。不出索然他问了一句如何获得solt内部的值,我直接沉默了。可能熟悉vue的小伙伴知道其实在学习或者开发的过程中很少说自己封装组件或者封装组件使用solt那都是一些很高阶的玩法,然后面试官看完打不出来就开始给我上课。。巴拉巴拉一大堆最后甚至聊到了后端的负载均衡,还说了一些如何进阶到高级前端的一些经验,我连连点头这个时候就产生了“崇拜感”,在此时他就完成了恐吓以及鼓励的阶段,然而他的玩法等级更高,他看到我连连点头就知道时机成熟了,可以进行下一步了。其实我点头的过程也是洗脑的一个步骤叫“行为认同”,他捕获到这个信号随后直接说给我上社保之类的不经意间告诉我我有希望,然后通知我第二天复试,其实现在寻思寻思那个时候其实他已经想录用我了,复试只不过是让我认为这个岗位来之不易,拴住人心为他卖力的手段。

工作一开始我确实是向被打了鸡血一样,老板让我5天做完的工作我一天就做完了,我寻思先在公司立一个FLAG,让我“崇拜的偶像”看看我的能力,回过头来我已经忘了我只是来一个面静态页开发的,不知觉之间做了很多其他的事情,并且与工资不符,其实在当时我并没有反应过来,后来自己复盘才发现都是套路。当然不是diss老板,其实他在我技术路上确实给了很多有效的指导,只是公司各种面试不进来人,一周面试了4个前端开发没有一个进来的,导致所有的活基本都是我一个人干,有点崩溃。最后无奈在家加班把一个项目的几个模板组件写完第二天带到公司直接分手。

不知不觉已经叭叭快2000字了,其实后面还想聊聊上面提到的"行为认同"还有一些其他的洗脑套路,比如“代入角色”,“集体行为”等,然而我现在寻思到的竟然是我为啥初中没有这个能力,轻轻松松叭叭这么多废话在我作文之上凑字数。

Vue使用Mook模拟数据

1.在项目根目录建立 vue.config.js

module.exports = {
  devServer: {
    before(app, server) {
      // get请求
      app.get("/api/cartList", (req, res) => {
        res.json({
          result: [
            {
              id: 0,
              title: "烤鸡翅",
              price: 15,
              active: true,
              count: 1
            },
            {
              id: 1,
              title: "烤腰子",
              price: 28,
              active: true,
              count: 1
            },
            {
              id: 2,
              title: "烤大蒜",
              price: 50,
              active: true,
              count: 1
            },
            {
              id: 3,
              title: "烤鸡脖",
              price: 88,
              active: true,
              count: 1
            }
          ]
        });
      });
    }
  }
};

随后重启项目 模拟出来的后端接口在你项目的地址/api/cartList

下载axiosmain.js全局挂载

import axios from "axios";
Vue.prototype.$http = axios;

最后在任意一个组件内使用 this.$http 进行axios请求

node.js中间件代理服务器跨域

const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const app = express();
// 设置允许跨域响应头
app.all("*", function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});

// http-proxy-middleware中间件
app.use(
  "/",
  createProxyMiddleware({
    target: "http://localhost:3000",
    changeOrigin: true
  })
);
app.listen(8080);

服务器3000端口,前端请求的是8080端口,产生了跨域行为u。使用中间件代理跨域服务器监听前端请求转发到服务器拿到结果随后把结果发送回客户端。

迷茫


18岁的失落,19岁的狂妄,20岁的迷茫。

正如你所见F是在这样的一个不到8平方米的空间内度过一个个无聊的日常。

每天醒来就是游戏上线做下日常,随后就是写程序,修炼“吃饭的技能”。

原本的爱好误打误撞的往职业的方向发展,说真的,好像开始有点讨厌了。但是又没有别的办法,仔细想想自己好像除了编程以外,好像啥都不会了。

19岁时的F经历过高考的失落他好像膨胀了,忘记了当时的心情,因为他又一次自以为战胜了身边所有人,其实自己原来是最傻逼的那个。

比中指对人就喊是个傻逼其实都是F自己内心对自己的嘲讽,毕竟身边都是“傻逼”自己也好不到哪去,没有比较就没有伤害,曾经自己瞧不起的人和身边的朋友都能养活自己了,而F还一无是处。

2020年的F_picacho

这段时间的F确实是成长的很快,从去年的前端基础入门到如今即将迈向全栈开发多多少少F对自己这段时间的表现比较满意。但是看着身边年龄比自己小的人,技术水平还在自己之上的人F还是倍感压力。

等这个阶段的NodeJS学习完F会去重构PikaPikaPOI项目。实现一个全栈项目。开学就面临实习的F抓紧一切时间“修炼”希望自己能强大到独当一面。

无论是设计还是前后端自己都可以完成(我就憧憬下?),那时候的F是不是就能让家里人放心了?

还是说F会悲惨到毕业就失业?

无所谓啦,我要滚去写代码麻痹自己了。

这可能就是迷茫吧。

React组件化 进阶

React组件化

1 Ant-Design第三方库介绍和按需加载方式

Ant-Design是React非常流行的第三方库,企业级产品设计体系,创造高效愉悦的工作体验。

官方网站

1.1 安装与初始化

1.1.1 安装

1.1.1.1 使用 npm

我们推荐使用 npm 或 yarn 的方式进行开发,不仅可在开发环境轻松调试,也可放心地在生产环境打包部署使用,享受整个生态圈和工具链带来的诸多好处。

npm install antd --save

当然了这里还是推荐国内用户使用淘宝的npm镜像使用cnpm命令安装或者是翻墙

1.1.1.2 使用yarn安装
yarn add antd

1.2初始化

既然我们已经安装成功就看看如何把对应的组件展示出来,例如我需要一个按钮。

这里我新生成了个项目,没有删除目录内容。

1.2.1 全局导入

首先在app.js中导入对应的组件以及样式库

import Button from 'antd/es/button/' //引入ant的按钮组件
import 'antd/dist/antd.css' //引入ant的样式库

然后在在任意一处位置使用ant的Button组件,详细内容看ant的文档

<Button type="primary">登录</Button>

虽然没用如何问题但是我们需要注意,在以后开发React项目的过程中这种导入方式是很消耗性能的,而且使用起来也不方便,因此ant给我们提供了一种解决方案即高级配置。

1.2.2 高级配置 按需加载

上面例子在实际开发中还有一些优化的空间,比如无法进行主题配置,而且上面的例子加载了全部的 antd 组件的样式(gzipped 后一共大约 60kb)。

此时我们需要对 create-react-app 的默认配置进行自定义,这里我们使用 react-app-rewired (一个对 create-react-app 进行自定义配置的社区解决方案)。

引入 react-app-rewired 并修改 package.json 里的启动配置。由于新的 [email protected] 版本的关系,你还需要安装 customize-cra

$ yarn add react-app-rewired customize-cra
# 使用[email protected]
$ yarn add react-app-rewired customize-cra@next
/* package.json */
"scripts": {
   "start": "react-app-rewired start",
   "build": "react-app-rewired build",
   "test": "react-app-rewired test",
}

然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。

const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
    fixBabelImports('import',{
        libraryName:'antd',
        libraryDirectory:'es',
        style:'css'
    })
)

使用 babel-plugin-import

注意:antd 默认支持基于 ES module 的 tree shaking,js 代码部分不使用这个插件也会有按需加载的效果。

babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件(原理),现在我们尝试安装它并修改 config-overrides.js 文件。

$ yarn add babel-plugin-import

经过了上面的一些配置我们就可以在组件中使用按需导入的方式

import { Button } from 'antd'

因为css在 config-overrides.js我们已经导出去了所以这里不需要再次导入,然后随便找一个位置就可以使用ant组件了!

真的是一劳永逸

2 聪明式组件&傻瓜式组件

基本原则:聪明组件(容器组件)负责数据获取,傻瓜组件(展示组件)负责根据根据props显示信息内容

优势:

  1. 逻辑和内容展示分离
  2. 重用性高
  3. 复用性高
  4. 易于测试

这里就将使用一个评论列表组件演示下什么是聪明组件什么是傻瓜组件

import React, { Component } from "react";
function Comment(props) {
  const { id, content, author } = props.comment;
  return (
    <div>
      <p>{id}</p>
      <p>{content}</p>
      <p>{author}</p>
    </div>
  );
}

class CommentList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      comments: []
    };
  }
  render() {
    setTimeout(() => {
      this.setState({
        comments: [
          {
            id: 1,
            author: "FaceBook",
            content: "React牛逼"
          },
          {
            id: 2,
            author: "尤雨溪",
            content: "Vue更牛逼"
          }
        ]
      });
    }, 1000);
    return (
      <div>
        {this.state.comments.map((item, i) => {
          return <Comment key={i} comment={item} />;
        })}
      </div>
    );
  }
}

export default CommentList;

2.1 聪明组件

CommentList就是一个聪明组件,我们可以看到它负责了数据获取并且给Comment提供了容器。

class CommentList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      comments: []
    };
  }
  render() {
    //模拟异步ajax请求数据
    setTimeout(() => {
      this.setState({
        comments: [
          {
            id: 1,
            author: "FaceBook",
            content: "React牛逼"
          },
          {
            id: 2,
            author: "尤雨溪",
            content: "Vue更牛逼"
          }
        ]
      });
    }, 1000);
    return (
      //组件传值
      <div>
        {this.state.comments.map((item, i) => {
          return <Comment key={i} comment={item} />;
        })}
      </div>
    );
  }
}

2.2 傻瓜组件

Comment在这里的角色就是一个傻瓜组件,他仅仅负责接收从CommentList来的数据然后渲染出对应的内容。

function Comment(props) {
  const { id, content, author } = props.comment;
  return (
    <div>
      <p>{id}</p>
      <p>{content}</p>
      <p>{author}</p>
    </div>
  );
}

所以在抽离组件的过程中需要思考这个组件将来是一个什么组件如果是一个傻瓜式组件我就让他单纯的展示,如果是一个聪明组件我们就需要使用class的方式声明,然后进行一些操作。

3 组件渲染优化解决方案

呐,上一节我们使用聪明式组件&傻瓜式组件实现了一个评论列表,现在的需求是我希望他可以实时更新,每一秒向服务器发起一次请求进行轮询更新,那这里就有人有想法了,不就是把setTimeout换成setInterval不就好了。

setInterval(() => {
  this.setState({
    comments: [
      {
        id: 1,
        author: "FaceBook",
        content: "React牛逼"
      },
      {
        id: 2,
        author: "尤雨溪",
        content: "Vue更牛逼"
      }
    ]
  });
}, 1000);

ennn,事实上这样是没错的,也可以进行轮询,但是有没有思考过,我1秒进行一次轮询,一天就是3600X24次的轮询,对服务器的压力以及对本地计算机的压力来说都很大(反正我的电脑现在已经开始嗡嗡的叫唤了)

对于React来说外界的数据不管有没有变化React都会进行更新,但是这个是我们不希望的,我们希望的是数据没用变化React就不进行render

3.1 shouldComponentUpdate 生命周期

这里我们可以使用生命周期里的一个钩子函数shouldComponentUpdate

所以在这里改造一下我们的Comment组件

class Comment extends Component {
  //性能优化点
  shouldComponentUpdate(nextProps, nextState, nextContext) {
    if (nextProps.comment.content === this.props.comment.content) {
      return false;
    } else {
      return true;
    }
  }

  render() {
    const { id, content, author } = this.props.comment;
    console.log("render");
    return (
      <div>
        <p>{id}</p>
        <p>{content}</p>
        <p>{author}</p>
      </div>
    );
  }
}

使用shouldComponentUpdate去判断一下数据有没有发生变化 如果没有发生变化就阻止重新渲染。

3.2 继承于PureComponent

//解构出PureComponent组件
import React, { Component, PureComponent } from "react";

随后我们的Comment组件改造成继承于PureComponent

class Comment extends PureComponent {
  render() {
    const { id, content, author } = this.props.comment;
    return (
      <div>
        <p>{id}</p>
        <p>{content}</p>
        <p>{author}</p>
      </div>
    );
  }
}

为什么要继承于PureComponent组件?是因为PureComponent内部重写了shouldComponentUpdate,当然这里好奇PureComponent是如何重写的,如何进行比较的这里就不展开聊了,有兴趣的小伙伴可以在node_modules目录中找到PureComponent组件看看。

但是注意!代码改造到这一步按理来说应该是没有问题了,但是调试过程中我们可以发现它还是在继续渲染,这是因为PureComponent是浅比较实现的函数类。

那我们的comments一个数组里嵌套对象这种显然使用浅比较无法得出结果。

那既然这样,我们是不是在父组件中在传值的过程中不传递一个复杂的数据结构了,我们直接传递对应的值是不是就可以解决了?

import React, { Component, PureComponent } from "react";

class Comment extends PureComponent {
  render() {
    console.log("render");
     // 2.修改字段,解构
    const { id, content, author } = this.props;
    return (
      <div>
        <p>{id}</p>
        <p>{content}</p>
        <p>{author}</p>
      </div>
    );
  }
}

class CommentList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      comments: []
    };
  }
  render() {
    setInterval(() => {
      this.setState({
        comments: [
          {
            id: 1,
            author: "FaceBook",
            content: "React牛逼"
          },
          {
            id: 2,
            author: "尤雨溪",
            content: "Vue更牛逼"
          }
        ]
      });
    }, 1000);
    return (
      <div>
        {this.state.comments.map((item, i) => {
          return (
            // 1.改造成传递简单的值
            <Comment
              key={i}
              id={item.id}
              content={item.content}
              author={item.author}
            />
          );
        })}
      </div>
    );
  }
}

export default CommentList;

经过调试后发现确实是解决了,而且效果还不错。

当然我们还可以使用ES6的剩余运算符去优化我们的代码,提升境界。

<Comment key={i} {...item} />

3.2 React.meom

注意这是一种高阶组件用法,具体的内容会在后面解释。

const Comment = React.memo(({ id, content, author }) => {
  return (
    <div>
      <p>{id}</p>
      <p>{content}</p>
      <p>{author}</p>
    </div>
  );
});

用法与PureComponent类似但是更适合函数式声明的组件,当然你要是类声明的组件也可以用。

4 组件组合而非继承实现代码重用

React有十分强大的组合模式,我们推荐使用组合而非继承实现组件间的代码重用。

这里设计一个Demo,有这样一个需求,我需要一个信息框的组件,这个信息框可重用的组件。

首先我新建了一个组件Compond

import React, { Component } from "react";

function Dialog(props) {
  //这里的children就跟Vue中的匿名插槽类似
  return <div>{props.children}</div>;
}
function WelcomeDialog() {
  return (
    <div>
      <Dialog>
        <h2>Hello</h2>
        <p>F_picacho</p>
      </Dialog>
    </div>
  );
}
class Compond extends Component {
  render() {
    return (
      <div>
        <WelcomeDialog />
      </div>
    );
  }
}

export default Compond;

这个过程就是我们将Dialog组件组合到了WelcomeDialog中,比如你还有他的信息框,例如提示,警告等等。

你看也可以传递一些其他的内容 例如样式属性,这里顺便写一下行内的写法。

import React, { Component } from "react";

function Dialog(props) {
  //这里的children就跟Vue中的匿名插槽类似
  return (
    <div style={{ border: `3px solid ${props.color}` }}>{props.children}</div>
  );
}
function WelcomeDialog() {
  return (
    <div>
      <Dialog color="pink">
        <h2>Hello</h2>
        <p>F_picacho</p>
      </Dialog>
    </div>
  );
}
class Compond extends Component {
  render() {
    return (
      <div>
        <WelcomeDialog />
      </div>
    );
  }
}

export default Compond;

甚至还能传递一个组件,这里就直接使用Ant的按钮作为例子

import React, { Component } from "react";
import { Button } from "antd";
function Dialog(props) {
  return (
    <div style={{ border: `3px solid ${props.color}` }}>
      {props.children}
      <div>{props.btn}</div>
    </div>
  );
}
function WelcomeDialog() {
  const infoBtn = <Button type="info">确认</Button>;
  return (
    <div>
      <Dialog color="pink" btn={infoBtn}>
        <h2>Hello</h2>
        <p>F_picacho</p>
      </Dialog>
    </div>
  );
}
class Compond extends Component {
  render() {
    return (
      <div>
        <WelcomeDialog />
      </div>
    );
  }
}

export default Compond;

这个感觉就又有点像Vue的具名插槽了,但是在React中没有插槽,这种方式叫做组合。

5 HOC高阶组件 !重要

组件设计的目的:保证功能的单一性。

高阶组件不是组件,本质上是一个函数,高阶组件接收一个组件或者多个组件,返回一个新的组件,则当前组件为高阶组件,好比就是你给我一个赛亚人到我的高阶组件里,我给你返回一个超级赛亚人。高阶组件对当前组件进行一系列的加工。

我们之前使用过一次React提供的高阶组件React.meom,这里就新建了一个Hoc.Js设计了一个Demo演示下如何自己写出一个高阶组件。

import React, { Component } from "react";
// 本质上是一个函数,高阶组件接收一个组件或者多个组件,返回一个新的组件.
const highOrderCom = Comp => {
  // 返回一个新组件
  const NewComponent = props => {
    // 属性代理
    const attr = { type: "高阶组件" };
    return <Comp {...props} {...attr}></Comp>;
  };
  return NewComponent;
};

class Hoc extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.type}</h3>
      </div>
    );
  }
}

// 抛出高阶组件加工后的新组件
export default highOrderCom(Hoc);

这是一种普通的高阶组件,把普通组件传递进去,添加了一些属性,我们还可以实现重写高阶组件内部的生命周期。

import React, { Component } from "react";

function highOrderCom(Comp) {
  return class extends Component {
    constructor(props) {
      super(props);
    }
    componentDidMount() {
      console.log("ajax请求");
    }
    render() {
      return <Comp {...this.props} name="react"></Comp>;
    }
  };
}

class Hoc extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.name}</h3>
      </div>
    );
  }
}

// 抛出高阶组件加工后的新组件
export default highOrderCom(Hoc);

我们以后可以使用高阶组件实现更加强大的动态功能

5.1 高阶组件的应用

5.1.1 打印日志的高阶组件实现和链式调用

import React, { Component } from "react";

// 打印日志高阶组件
const withLog = Comp => {
  console.log(Comp.name + "渲染了");
  const newCom props => {
    return <Comp {...props} />;
  }
  return newCom;
};

function highOrderCom(Comp) {
  return class extends Component {
    constructor(props) {
      super(props);
    }
    componentDidMount() {
      console.log("ajax请求");
    }
    render() {
      return <Comp {...this.props} name="react"></Comp>;
    }
  };
}

class Hoc extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.name}</h3>
      </div>
    );
  }
}

// 抛出高阶组件加工后的新组件 从内到外外 链式调用
export default highOrderCom(withLog(withLog(Hoc)));

最终结果应该是三条分别是'Hoc渲染了','newCom'渲染了以及'ajax请求'发生的顺序也跟代码里最后一样 从内到外链式调用,但是这么写多多少少有那么一丝丝奇怪,像套娃一样,所以这里再推一个ES7的写法,装饰器。

首先我们要安装一个babel插件进行语法转换

npm install --save-dev babel-plugin-transform-decorators-legacy @babel/plugin-proposal-decorators

下载完成后在config-overrides.js修改对应配置

const {
  override,
  fixBabelImports, //按需加载配置函数
  addBabelPlugins //babel插件配置函数
} = require("customize-cra");
module.exports = override(
  fixBabelImports("import", {
    libraryName: "antd",
    libraryDirectory: "es",
    style: "css"
  }),
  addBabelPlugins(["@babel/plugin-proposal-decorators", { legacy: true }]) //支持装饰器

然后react就可以支持更加高级的语法了

在使用装饰器之前我们应该明确我们对谁装饰就在谁之前写装饰器的语法,例如我们要装饰的是Hoc所以就在Hoc之前写

import React, { Component } from "react";

// 打印日志高阶组件
const withLog = Comp => {
  console.log(Comp.name + "渲染了");
  const newCow = props => {
    return <Comp {...props} />;
  };
  return newCow;
};

function highOrderCom(Comp) {
  return class extends Component {
    constructor(props) {
      super(props);
    }
    componentDidMount() {
      console.log("ajax请求");
    }
    render() {
      return <Comp {...this.props} name="react"></Comp>;
    }
  };
}
//注意顺序 由下到上
@highOrderCom
@withLog
@withLog
class Hoc extends Component {
  render() {
    return (
      <div>
        <h3>{this.props.name}</h3>
      </div>
    );
  }
}
// 抛出高阶组件加工后的新组件 从内到外外 链式调用
export default Hoc;

5.1.2 页面复用

比如我现在有一个电影列表它有两个分类A,B,A和B都是同样的样式页面里面的数据不同。

MovieA.js MovieB.js

import React, { Component } from "react";

class MovieA extends Component {
  constructor(porps) {
    super(porps);
    this.state = {
      movies: []
    };
  }
  componentDidMount() {
    // 发起ajax请求获取数据
    this.setState({
      movies
    });
  }

  render() {
    return (
      <div>
        <MovieList movies={this.state.movies}> </MovieList>
      </div>
    );
  }
}

export default MovieA;
//AB组件内容一致 这里就拿出一个

MovieList.js

import React, { Component } from "react";

class MovieList extends Component {
  render() {
    return (
      <div>
        <ul>
          {this.props.movies.map((item, i) => {
            return <li key={i}>{item.title}</li>;
          })}
        </ul>
      </div>
    );
  }
}

export default MovieList;

但是注意,随着项目的越来越大,数据的增加,这两种类别显然是无法满足项目。如果说有100种电影种类难道要写100个这样组件?呐在Vue中我们使用mixin进行混入,在React中我们可以使用高阶组件。

我新建立了一个文件夹HOC专门存放高阶组件,并且新建了一个文件WithFetch.js

import React, { Component } from "react";
export const WithFetch = fetch => Comp => {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        data: []
      };
    }
    componentDidMount() {
      //模拟数据来源
      if (fetch === "A") {
        this.setState({
          data: [
            {
              id: 1,
              title: "《除日本岛以外全部沉没》",
              category: "A"
            },
            {
              id: 2,
              title: "《白给秀》",
              category: "A"
            }
          ]
        });
      } else {
        this.setState({
          data: [
            {
              id: 1,
              title: "《上海堡垒》",
              category: "B"
            },
            {
              id: 2,
              title: "《甜蜜暴击》",
              category: "B"
            }
          ]
        });
      }
    }
    render() {
      return <Comp {...this.props} data={this.state.data}></Comp>;
    }
  };
};

MovieA.js

import React, { Component } from "react";
import MovieList from "./MovieList";
import { WithFetch } from "../HOC/WithFetch";
@WithFetch("A")
class MovieA extends Component {
  render() {
    return (
      <div>
        <MovieList movies={this.props.data}> </MovieList>
      </div>
    );
  }
}

export default MovieA;

5.1.3 权限控制

有这样一个场景,一个网站有一些页面或者组件只能admin身份的用户才能访问,普通用户无法访问。

首先我在HOC目录下新建了一个WithAdmin.js作为判断用户权限的高阶组件,重写了componentDidMount()生命周期函数,模拟了一个登录状态,且给登录状态赋值,随后render中如果权限匹配则返回一个组件权限不足返回一个提示组件。

import React, { Component } from "react";
export const WithAdminAuth = Comp => {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        isAdmin: false
      };
    }
    componentDidMount() {
      // 模拟权限获取
      const currentRole = "Admin";
      this.setState({
        isAdmin: currentRole === "Admin"
      });
    }
    render() {
      if (this.state.isAdmin) {
        return <Comp> {...this.props}>Admin</Comp>;
      } else {
        return <div>权限不足</div>;
      }
    }
  };
};

随后我在某一些组件中想给附加一个权限检测,则我就在哪一个组件引用这个高阶组件。

import React, { Component } from "react";
import { WithAdminAuth } from "../HOC/WithAdmin";
@WithAdminAuth
class PageA extends Component {
  render() {
    return <div></div>;
  }
}

export default PageA;

如果权限是Admin则会显示一些组件,写到这里我们的这个组件算是完成了,但是为了让组件更加健壮一点我们还得改造下,毕竟一个网站有可能有很多权限组。所以我们把组件改造成这样。

import React, { Component } from "react";
export const WithAdminAuth = role => Comp => {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        isAdmin: false
      };
    }
    componentDidMount() {
      // 模拟权限获取
      const currentRole = "Admin";
      this.setState({
        isAdmin: currentRole === role
      });
    }
    render() {
      if (this.state.isAdmin) {
        return <Comp {...this.props}></Comp>;
      } else {
        return <div>权限不足</div>;
      }
    }
  };
};

role用来接收参数进行比较,也可以定义多个权限,然后改造这个组件就好了,来一个或判断就搞定了

5.2 高阶组件总结

写了这么多先总结一波我们为什么要用高阶组件

  1. react高阶组件能够让我们写出更加易于维护的react代码

高阶组件是什么?

  1. 本质上是函数,这个函数接收一个或者多个组件,返回一个新组件

如何实现高阶组件

  1. 属性代理是最常见的方式
  2. 反向继承

6 组件通信 Context

Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的方法。在一个典型的React应用中,数据是通过props属性自上而下(由父到子)进行传递的,但这种做法对于某些属性而言是极其繁琐的(例如:地区偏好,UI主题),这些这些属性是应用程序中许多组件都需要的。Context提供了一种组件间共享此类值的方式,而不必显示的通过组件树的逐层传递props

6.1 何时使用Context

Context设计是为了共享那些全局属性,例如当前认证的用户、主题等。举个例子:

import React, { Component } from "react";
import { Button } from "antd";
const ThemeContext = React.createContext();

class ThemeBtn extends Component {
  constructor(props) {
    super(props);
  }
  // 首先定制当前创建的上下文对象 为当前实例的静态属性
  // 在渲染的方法中使用 this.context获取共享数据
  static contextType = ThemeContext;
  render() {
    return <Button type={this.context.type}>{this.context.name}</Button>;
  }
}
function Toolbar(props) {
  return <ThemeBtn></ThemeBtn>;
}
// Context === Vue的provide和inject 在React中用的是provider和consumer
class ContextSimple extends Component {
  constructor(props) {
    super(props);
    this.state = {
      stroe: {
        type: "primary",
        name: "按钮"
      }
    };
  }
  render() {
    return (
      <div>
        <ThemeContext.Provider value={this.state.stroe}>
          <Toolbar></Toolbar>
        </ThemeContext.Provider>
      </div>
    );
  }
}

export default ContextSimple;

但是这样选题略显复杂,还有一种相对简便的函数式渲染方式

import React, { Component } from "react";
import { Button } from "antd";
const ThemeContext = React.createContext();

class ThemeBtn extends Component {
  constructor(props) {
    super(props);
  }
  // static contextType = ThemeContext;
  render() {
    // return <Button type={this.context.type}>{this.context.name}</Button>;
    return (
      <ThemeContext.Consumer>
        {value => {
          return <Button type={value.type}>{value.name}</Button>;
        }}
      </ThemeContext.Consumer>
    );
  }
}
function Toolbar(props) {
  return <ThemeBtn></ThemeBtn>;
}
// Context === Vue的provide和inject 在React中用的是provider和consumer
class ContextSimple extends Component {
  constructor(props) {
    super(props);
    this.state = {
      stroe: {
        type: "primary",
        name: "按钮"
      }
    };
  }
  render() {
    return (
      <div>
        <ThemeContext.Provider value={this.state.stroe}>
          <Toolbar></Toolbar>
        </ThemeContext.Provider>
      </div>
    );
  }
}

export default ContextSimple;

但是注意!这样进行传值他的属性名必须为value否则报错

虽然第二种方式比第一种好理解了一些但是可以想象如果一个更加复杂的项目你需要创建多个这样的上下文,这样仍然很复杂,解决的方案就是嵌入Redux插件它类似于Vue的VueX这个放到以后去聊。

6.2组件通信高阶组件装饰器写法

编写的过程中我们不难发现有很多的东西都是公有的,所以我们可以写两个高阶组件一个提供者一个消费者。

首先新建一个index.js在HOC目录下

import React, { Component } from "react";
const ThemeContext = React.createContext();

//提供者
export const withPriovider = Comp => {
  return class extends Component {
    constructor(props) {
      super(props);
      // 共享的数据
      this.state = {
        stroe: {
          type: "primary",
          name: "按钮"
        }
      };
    }
    render() {
      return (
        <ThemeContext.Provider value={this.state.stroe}>
          <Comp {...this.props}></Comp>
        </ThemeContext.Provider>
      );
    }
  };
};

//消费者
export const withConsumer = Comp => {
  return class extends Component {
    render() {
      return (
        <ThemeContext.Component>
          {value => <Comp {...this.props} value={value}></Comp>}
        </ThemeContext.Component>
      );
    }
  };
};

在使用的时候

import React, { Component } from "react";
import { Button } from "antd";
import { withPriovider, withConsumer } from "../HOC/index";
@withConsumer
class ThemeBtn extends Component {
  render() {
    return (
      <Button type={this.props.value.type}>{this.props.value.name}</Button>
    );
  }
}
function Toolbar(props) {
  return <ThemeBtn></ThemeBtn>;
}
@withPriovider
class ContextSimple extends Component {
  render() {
    return <Toolbar></Toolbar>;
  }
}
export default ContextSimple;

结束

在这一个部分把React的最核心的内容高阶组件写出来了顺便还写了一些按需引入的方式渲染优化等等。。。至此React高阶组件部分结束,接下来就是React全家桶的部分,全家桶写完就可以做项目了。

首发于:F_picachoの领域

同步发布于:掘金

作者:F_picacho

技术指导:我的两位师傅 @刘建伟 @Mjj

感谢:@白菜 对Blog的运营以及维护

文档下载:https://ww.lanzous.com/ictlede

2020/05/20

React基础

React基础

1 起步

1.1 上手

  • npm i -g create-react-app 安装官方脚手架
  • create-react-app demo 初始化

1.2 文件结构

1.3 启动项目

package.jsom中可以看到如下一段内容

"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
}

在控制台中cd到项目根目录下输入启动指令(这里以NPM管理工具为例yarn同理)

  • npm run start 在本地启动一个服务器 预览项目 默认会在http://localhost:3000地址下运行
  • npm run build 项目打包编译
  • npm run test 在交互式监视模式下启动测试运行程序
  • npm run eject !!!注意:这是单向操作。一旦你弹出,你就不能回去了。

eact-scripts 是 create-react-app 的一个核心包,一些脚本和工具的默认配置都集成在里面,而 yarn eject 命令执行后会将封装在 create-react-app 中的配置全部反编译到当前项目,这样用户就能完全取得 webpack 文件的控制权。所以,eject 命令存在的意义就是更改 webpack 配置存在的,如果有此类需要请自行理解下方react提供的原文解释,但是在通常的开发项目当中我们并不会使用到。

If you aren’t satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.

Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.

You don’t have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.

2 React启动流程

为了更好的理解React的启动流程我们通常建议新手删除 src 文件下的所有内容,自己手写构建出一个新的React应用。

当然你如果你删除了src 文件下的所有内容自然会报出一个错误即:

Could not find a required file.
  Name: index.js
  Searched in: C:\Users\23140\Desktop\reactCli\poi\src

没有找到index.js文件,也就是说index.js是React主要的入口文件我们在src下新建index.js并且写如下代码

src -> index.js

// React项目的入口文件
import React from "react"; //处理逻辑相关
import ReactDOM from "react-dom"  //处理DOM相关 注意react-dom依赖于react所以必须先引入react

// 引入完成后 ReactDOM 会抛出一个rende()方法
// rende()有两个参数 第一个参数为JSX对象,第二个参数为插入到哪里
ReactDOM.render(<h1>Hello React</h1>,document.querySelector('#root'));
// 注意第一个参数并非是HTML标签而是一个JSX对象这个会在下面解释,至于为什么第二个参数要插入到ID为root的标签下可以在public->index.html中找到答案

3 JSX介绍和模板语法渲染

3.1 什么是JSX

JSX = JavaScript + XML

JavaScript 能看到这里大家都很熟悉是什么不必多说

HTML是XML中的一种 所以现阶段可以把XML简单的认为XML是JS与HTML的一个整合

import React from "react";
import ReactDOM from "react-dom";

const ele = <h1>Hello React</h1>;
console.log(ele); //在JS中直接写HTML语法,打印结果是一个对象,这种对象我们称之为虚拟DOM元素或者JSX对象
ReactDOM.render(ele, document.querySelector("#root"));

第四行console输出结果:

YcC1C4.png

3.2模板语法

在React中我们使用{}进行插值例如这样

...
const ele = <h1>Hello {'F_picacho'}</h1>;
ReactDOM.render(ele, document.querySelector("#root"));

嗯这点跟Vue的双大括号插值很像,让我们试试能不能进行表达式运算例如这样

...
const ele = <h1>Hello {221+2}</h1>;
ReactDOM.render(ele, document.querySelector("#root"));

或者说我像要插入一个格式化名字的函数

import React from "react";
import ReactDOM from "react-dom";

const user = {
  fristName: "F",
  lastName: "picacho"
};

function formatName(user) {
  return user.fristName + user.lastName;
}
const ele = <h1>Hello {formatName(user)}</h1>;
ReactDOM.render(ele, document.querySelector("#root"));

当然你也可以在某个方法里返回一个JSX对象或者直接使用三元运算做判断这里就不一一展开了,你能想到的都可以插。!!!但是注意 render只能接受一个闭合标签这点跟Vue一样所以你这个模板中拥有多个标签必须在最外层套一个标签组织成一个标签闭合例如

import React from "react";
import ReactDOM from "react-dom";

const ele = (
  <div>
    <h1>Hello World</h1>
    <h2>Hello React</h2>
    <h3>Hello Vue</h3>
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

3.3 元素渲染

元素是构成React应用的最小砖块例如:

const ele = <h1>Hello {'F_picacho'}</h1>;

与浏览器的DOM元素不同,React元素是创建开销极小的普通对象。ReactDOM会负责更新DOM来与React元素保持一致。

ReactDOM.render()其实就是在渲染DOM节点

3.1 React只更新他需要更新的部分

这里写了一个计时器的例子

import React from "react";
import ReactDOM from "react-dom";

function tick() {
  const ele = (
    <div>
      <h1>当前的事件是:</h1>
      <h2>{new Date().toLocaleTimeString()}</h2>
    </div>
  );
  ReactDOM.render(ele, document.querySelector("#root"));
}
setInterval(tick, 1000);

YcFPUK.png

我们可以看到React只为我们更新了我们需要更新的部分。

4 循环绑定&过滤元素

4.1循环

4.1.1循环数组

在列表中显示数组元素肯定要用到循环 至于为什么不用foreach是因为foreach无法返回而map可以进行返回的操作我们要循环出的对象是li 所以就插值利用map取到数组中的元素然后返回对应的JSX对象。

!!!注意这里与Vue相同必须给循环绑定的JSX元素绑定key属性,区分不同的元素,否则直接报错

import React from "react";
import ReactDOM from "react-dom";

const arr = [1231, 233, 15, 123, 15, 15, 1, 1, 56, 12, 156];
const ele = (
  <div>
    <ul>
      {arr.map((item, index) => {
        return <li key={index}>{item}</li>;
      })}
    </ul>
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

4.2.2循环对象

import React from "react";
import ReactDOM from "react-dom";

const arr = { name: "F_picacho", age: 18 };
const ele = (
  <div>
    <ul>
      {Object.keys(arr).map((item, index) => {
        return <li key={index}>{item}</li>;
      })}
    </ul>
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

4.2过滤

这里模拟了一组json数据goods大于等于500的商品显示 否则不显示

import React from "react";
import ReactDOM from "react-dom";

const goods = [
  { id: 1, price: 100, title: "小米1" },
  { id: 2, price: 200, title: "小米2" },
  { id: 3, price: 500, title: "小米3" },
  { id: 4, price: 1000, title: "小米4" },
  { id: 5, price: 3000, title: "小米5" }
];
const ele = (
  <div>
    <ul>
      {goods.map(good => {
        return good.price >= 500 ? <li key={good.id}>{good.title}</li> : null;
      })}
    </ul>
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

5 React中创建组件

随着项目越做越大需要的JSX对象也越来越多所以的JSX对象堆在index.js会造成代码冗余,导致后期维护困难。

React核心的思想就是组件化开发我们可以利用组件降低代码冗余提高复用效率。

5.1函数声明组件

import React from "react";
import ReactDOM from "react-dom";

//函数声明
function Welcome() {
  return <h2>Hello React</h2>;
}

const ele = (
  <div>
    <Welcome /> //引用直接使用函数名标签引用 可以写单闭合标签也可以写双闭合标签
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

!!!注意组件名无论使用哪一种声明方式一定要大写否则报错,函数式声明的组件必须返回一个JSX对象

5.1.1 函数声明组件传值

组件通常都跟他的属性是有关联的 我们可以在组件引用的位置传入一些自定义属性 并且使用props接收

import React from "react";
import ReactDOM from "react-dom";

//函数声明
function Welcome(props) {
  return <h2>Hello {props.name}</h2>;
}
const ele = (
  <div>
    <Welcome name="F_picacho" />
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

5.2类声明组件

import React from "react";
import ReactDOM from "react-dom";


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

const ele = (
  <div>
    <Welcome name="F_picacho" />
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

新建一个类继承于 React.Component 且必须有render()函数将虚拟DOM转换为真实DOM并且return一个JSX元素

5.2.1 类声明组件传值

使用this.props.在标签定义的自定义属性如果类中有constructor需要将props继承下来即:

class Welcome extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <h1>{this.props.name}</h1>;
  }
}

5.2.2 类声明组件模块化引入

将上面的代码剪切并且在src下建立一个新的文件Header.js

src->Header.js

import React from "react";

class Welcome extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <h1>{this.props.name}</h1>;
  }
}

export default Welcome;  //抛出Welcome

在index.js中引入

src->index.js

import React from "react";
import ReactDOM from "react-dom";

import Header from "./Header"; //引入模块

const ele = (
  <div>
    <Header name="F_picacho" />
  </div>
);
ReactDOM.render(ele, document.querySelector("#root"));

可以通过rcc指令快速生成类声明组件模板 webstrom自带 vscode需要安装拓展插件

6 复用组件

在同一个组件中可以抽离出任意层次的小结构,这种小的结构我们称之为复用组件。例如网页中的按钮,表单,对话框,甚至整个页面的内容。

  1. 将多个组件进行整合例如调用两次以上的组件
  2. 结构非常复杂时需要将组件拆分成小组件
  3. 会存在父子关系的数据传递

现在比如说我有一个这样的组件,我们可以发现这三个button组件是重复的不同的就是内容或者是里面的逻辑,这种的组件我们就可以把他们拆分出来。

//拆分前
class Welcome extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <button>提交</button>
        <button>删除</button>
        <button>清空</button>
      </div>
    );
  }
}
//拆分后
import React, { Component } from "react"; //这里将Component解构了出来方便后续使用

class Mybutton extends Component {
  render() {
    return (
      <div>
        <button>{this.props.title}</button>
      </div>
    );
  }
}

class Welcome extends React.Component {
  render() {
    return (
      <div>
        <Mybutton title="提交" />
        <Mybutton title="删除" />
        <Mybutton title="清空" />
      </div>
    );
  }
}

6.1 如何在React项目中提取组件

比如说我这里有一个这样的表单组件

import React, { Component } from "react";

class Welcome extends React.Component {
  constructor(props) {
    super(props);
      //这里依然被模拟的是从后端来的数据
    this.user = {
      name: "F_picacho",
      content: "这是我的React组件",
      date: new Date().toLocaleTimeString()
    };
  }
  render() {
    return (
      <div>
        <div className="comment">
          {/*用户信息*/}
          <div className="userinfo">
            <p>用户信息:{this.user.name}</p>
          </div>
          {/*评论内容*/}
          <div className="comment-container">
            <p>评论内容:{this.user.content}</p>
          </div>
          {/*评论时间*/}
          <div className="comment-time">
            <p>发布时间:{this.user.date}</p>
          </div>
        </div>
      </div>
    );
  }
}

export default Welcome;

这样的组件乍看下没有什么问题,但是我们这个例子是模拟的真是开发项目当中这个组件肯定是很复杂的,那这个时候我们就需要进行拆分以便于我们后期维护。

我们可以这样拆分上面的组件 首先肯定是要把comment抽离出来然后继续拆分把userinfo comment-container comment-time抽离拆分,例如这样:

import React, { Component } from "react";

class Comment extends Component {
  render() {
    return (
      <div className="comment">
        {/*用户信息*/}
        <div className="userinfo">
          <p>用户信息:{this.props.user.name}</p>
        </div>
        {/*评论内容*/}
        <div className="comment-container">
          <p>评论内容:{this.props.user.content}</p>
        </div>
        {/*评论时间*/}
        <div className="comment-time">
          <p>发布时间:{this.props.user.date}</p>
        </div>
      </div>
    );
  }
}

class Welcome extends React.Component {
  constructor(props) {
    super(props);
    this.user = {
      name: "F_picacho",
      content: "这是我的React组件",
      date: new Date().toLocaleTimeString()
    };
  }
  render() {
    return (
      <div>
        <Comment user={this.user} />
      </div>
    );
  }
}

export default Welcome;

这样就把Comment组件抽离出来了,当然你需要注意一下数据流向以及如何获取数据我们还可以把用户信息,评论内容,时间继续往外抽离例如这样:

import React, { Component } from "react";

class Userinfo extends Component {
  render() {
    return (
      <div className="userinfo">
        <p>用户信息:{this.props.user.name}</p>
      </div>
    );
  }
}

class Commentcontainer extends Component {
  render() {
    return (
      <div className="comment-container">
        <p>评论内容:{this.props.user.content}</p>
      </div>
    );
  }
}

class Commenttime extends Component {
  render() {
    return (
      <div className="comment-time">
        <p>发布时间:{this.props.user.date}</p>
      </div>
    );
  }
}

class Comment extends Component {
  render() {
    return (
      <div className="comment">
        {/*用户信息*/}
        <Userinfo user={this.props.user} />
        {/*评论内容*/}
        <Commentcontainer user={this.props.user} />
        {/*评论时间*/}
        <Commenttime user={this.props.user} />
      </div>
    );
  }
}

class Welcome extends React.Component {
  constructor(props) {
    super(props);
    this.user = {
      name: "F_picacho",
      content: "这是我的React组件",
      date: new Date().toLocaleTimeString()
    };
  }
  render() {
    return (
      <div>
        <Comment user={this.user} />
      </div>
    );
  }
}

export default Welcome;

这样我们就把一个表单组件拆分成很多个细小的组件,后期维护也很容易,哪里需要修改就重写哪里的代码就可以了

7 组件引入样式

呐前面说了这么多一直没用说过关于React如何组织样式的问题,这里就交代一下,其实就是写一个CSS然后将其使用模块化引入这也体现了React一切皆模块的思想

在外部新建一个css文件在组件中使用import引入即可import "./demo.css";

8 父子组件通信

在开发项目的过程中我们经常需要进行组件传值例如一个购物车的页面,显示购买数量是一个组件,加入购物车是一个组件,我在点击加入购物车的组件显示购买数量的组件内容就+1这里就涉及到了组件通信。组件通信又分为几种情况例如父组件->子组件子组件->父组件平行组件(兄弟组件),跨组件这四种。

8.1 父组件->子组件

其实父往子组件传值我们上面的demo写过很多次了父组件中的数据利用自定义属性进行传递,一层一层的传递到子组件中,类声明子组件使用this.props.自定义属性名接收,例如:

class Comment extends Component {
  render() {
    return (
      <div className="comment">
        {/*用户信息*/}
        <Userinfo user={this.props.user} />
        {/*评论内容*/}
        <Commentcontainer user={this.props.user} />
        {/*评论时间*/}
        <Commenttime user={this.props.user} />
      </div>
    );
  }
}

class Welcome extends React.Component {
  constructor(props) {
    super(props);
    this.user = {
      name: "F_picacho",
      content: "这是我的React组件",
      date: new Date().toLocaleTimeString()
    };
  }
  render() {
    return (
      <div>
        <Comment user={this.user} />
      </div>
    );
  }
}

注意父组件往子组件只能一层层的传递,因为React遵循的是单项数据流。可能你会觉得组件一但嵌套的层数多了之后这样通信显得很麻烦,在后面的内容会介绍如何解决这一问题。

  <div>
    <Comment user={this.user} />
  </div>

​ );
}
}

8.2 子组件->父组件

子往父传值的思路是子组件通过this.props触发一个从父组件中定制的方法进行传值例如:

import React, { Component } from "react";

class Comment extends Component {
  // 4.这时我们就可以通过this.props拿到父组件中的add事件并且在子组件中触发
  CommentClick() {
    this.props.add();
  }
  render() {
    // 3.子组件中使用原生js的方式绑定事件
    return (
      <div className="comment">
        <button onClick={this.CommentClick}></button>
      </div>
    );
  }
}

class Welcome extends React.Component {
  // 1.父组件中定义事件
  add() {
    alert("父组件的事件");
  }
  render() {
      // 2.自定义属性将父组件中的事件绑定传递出去
    return (
      <div>
        <Comment add={this.add} />
      </div>
    );
  }
}

export default Welcome;

这代码乍看没用问题,但实际上我们测试的过程中点击按钮后会抛出一个错误即:

YcCI2j.md.png

!!!类型错误propsundefined,发生这种错误我们就该意识到了这个this到底指向了哪里?呐js基础扎实的话就应该意识到这里的一个坑CommentClick里的this其实是指向了事件触发的对象而不是Comment组件所以错误就产生了。

所以说我们要改变当前this的指向,那这里直接介绍一种最直接的方式就是使用ES6中的箭头函数,箭头函数会根据作用域链往上查找最顶层的对象所以我们把事件改造成一个箭头函数就可以了。

CommentClick = () => {
  this.props.add();
};

不管是在Vue中还是React中使用箭头函数都可以避免this指向错误的问题。

呐,进行到了这里我们已经实现了父组件中定义的事件被子组件调用,下一步就是子组件提供出来的值传递到父组件中。

import React, { Component } from "react";

class Comment extends Component {
  CommentClick = () => {
    this.props.add("子组件中的数据");
  };
  render() {
    return (
      <div className="comment">
        <button onClick={this.CommentClick}>点我</button>
      </div>
    );
  }
}

class Welcome extends React.Component {
  // 父组件接收从子组件传递过来的数据
  add(val) {
    alert(val);
  }
  render() {
    return (
      <div>
        <Comment user={this.user} add={this.add} />
      </div>
    );
  }
}

export default Welcome;

9 React中的状态state

每一个组件中都会维护他自己的一个状态,什么是状态?说白了就是组件中维护的数据。

状态一般都会声明在constructor构造函数当中,假设我定制一个状态count为0即:

class Welcome extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <h1>{this.state.count}</h1>
      </div>
    );
  }
}

export default Welcome;

这样我们的count状态就会显示到h1标签中

呐假设我当前组件有一个按钮,单击后count值每次+1可以这么实现:

!!!注意只有类声明的组件才拥有state函数式声明的没有,在constructor中可以直接修改state在其他地方修改状态必须使用setState

import React, { Component } from "react";

class Welcome extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  add() {
    this.setState({
      count: this.state.count+=1
    });
  }
  render() {
    return (
      <div>
        <h1>{this.state.count}</h1>
        <button onClick={this.add}>+1</button>
      </div>
    );
  }
}

export default Welcome;

随后错误就来了

YcPFZ6.png

又是一个this指向问题,这个是毫无疑问的因为当前add方法里的this是指向调用它的对象而非Welcome本身,解决这类问题我们有以下几种手段

  1. 在constructor使用this绑定 this.add = this.add.bind(this)

  2. <button onClick={this.add.bind(this)}>+1</button>
    
  3. 把add()改造成箭头函数 add=()=>{} !!!推荐

  4. <button onClick={()=>this.add}>+1</button>
    

9.1 setState使用

组件中状态的来源有两种一种是外界的状态我们可以通过props接收,另外一种就是组件自身的状态state,如果我想修改state的状态就必须使用setState

setState是一个异步操作,在数据发生改变后你立即调用他你得到的结果可能不是你先要的结果,在React当中我们可以这样解决:

  add = () => {
    this.setState(
      (prevState, prevProps) => ({
        count: prevState.count + 1
      }),
      () => {
        console.log(this.state.count);
      }
    );
  };

setState这个方法第一个参数是一个箭头函数得返回一个对象,函数内接收两个参数prevState(之前的状态)和prevProps(之前的属性)。第二个也是一个回调函数最新的状态可以在第二个回调函数中获取到。

所以用函数式进行修改永远都不会出错,对象形式修改可能会产生异步问题,获取不到正确的状态。

10 React生命周期

YcPmzd.png

如图,React生命周期主要包括三个阶段:初始化阶段、运行中和销毁阶段,在Reaact不同的生命周期里,会依次触发不同的钩子函数。

具体每一个钩子函数触发后会发生什么,什么阶段适合触发什么样的方法以后会跟进。

11 受控组件与非受控组件

11.1受控组件

受状态控制的组件叫受控组件,需要与状态进行相应的绑定。

class Controlnput extends Component {
  constructor(props) {
    super(props);
    this.state = {
      val: ''
    };
  }
  handleChange = (e)=>{
      let val = e.target.value;
      this.steState({
          val
      })
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.val} onChange={this.handleChange}/>
      </div>
    );
  }
}
  • 受控组件必须要有一个onChange事件
  • 受控组件可以赋予默认值
  • 可以使用受控组件实现双向绑定

11.2 非受控组件

class NoControlnput extends Component {
  constructor(props) {
    super(props);
    this.state = {
      val: ''
    };
  }
  handleChange = ()=>{
      let val = this.refs.a.value;
      this.steState({
          val
      })
  }
  render() {
    return (
      <div>
        <input type="text" onChange={this.handleChange} ref="a"/>
        <h2> {this.state.val} </h2>
      </div>
    );
  }
}

12 表单的使用

在开发的过程中经常遇到需要表单的需求,在这里单独抽离出来一个单元和大家分享下表单的基本使用方法。

一个简单的用户登录Demo

import React, { Component } from "react";

class FormSimple extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: ""
    };
  }
  handleUsername = e => {
    this.setState({
      username: e.target.value
    });
  };
  handlePassword = e => {
    this.setState({
      password: e.target.value
    });
  };
  handelSubmit = e => {
    e.preventDefault();
    if (this.state.username && this.state.password) {
      // 发起ajax请求;
      alert(`当前用户名:${this.state.username}当前密码:${this.state.password}`);
    }
  };
  render() {
    return (
      <div>
        <form onSubmit={this.handelSubmit}>
          <p className="username">
            <label htmlFor="name">用户名:</label>
            <input
              type="text"
              value={this.state.username}
              onChange={this.handleUsername}
              id="name"
            />
          </p>
          <p className="password">
            <label htmlFor="password">密码:</label>
            <input
              type="password"
              value={this.state.password}
              onChange={this.handlePassword}
              id="pwd"
            />
          </p>
          <input type="submit" value="登录" />
        </form>
      </div>
    );
  }
}

export default FormSimple;

这里的代码就不多解释了,唯一需要注意的是记得清除form表单的默认事件,使用javascript:;或者使用preventDefault()值得注意的是这是一个受控组件。

这里再多演示一个标签

import React, { Component } from "react";

class FormSimple extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "",
      password: "",
      selectedArr: []
    };
  }
  handleUsername = e => {
    this.setState({
      username: e.target.value
    });
  };
  handlePassword = e => {
    this.setState({
      password: e.target.value
    });
  };
  handelSubmit = e => {
    e.preventDefault();
    if (this.state.username && this.state.password) {
      // 发起ajax请求;
      let arr = this.state.selectedArr;
      alert(
        `当前用户名:${this.state.username}当前密码:${this.state.password},${arr}`
      );
    }
  };
  handleChange = e => {
    let newArr = [...this.state.selectedArr];
    newArr.push(e.target.value);
    this.setState({
      selectedArr: newArr
    });
  };
  render() {
    return (
      <div>
        <form onSubmit={this.handelSubmit}>
          <p className="username">
            <label htmlFor="name">用户名:</label>
            <input
              type="text"
              value={this.state.username}
              onChange={this.handleUsername}
              id="name"
            />
          </p>
          <p className="password">
            <label htmlFor="password">密码:</label>
            <input
              type="password"
              value={this.state.password}
              onChange={this.handlePassword}
              id="pwd"
            />
          </p>
          <input type="submit" value="登录" />
          <p></p>
          我的爱好:
          <select
            multiple
            value={this.state.selectedArr}
            onChange={this.handleChange}
          >
            <option value="smoking">抽烟</option>
            <option value="drink">喝酒</option>
            <option value="tangto">烫头</option>
          </select>
        </form>
      </div>
    );
  }
}

export default FormSimple;

结束

呐,在这里React基础部分就已经写的差不多了往后还会继续跟进React的进阶内容,例如组件化啦,全家桶啦,常用的第三方库之类的,在这一节就算告一段落了往后随着我的不断学习也会在Blog里更新更多的内容。

首发于:F_picachoの领域

同步发布于:掘金

作者:F_picacho

技术指导:我的两位师傅 @刘建伟 @Mjj

感谢:@白菜 对Blog的运营以及维护

文档下载:https://ww.lanzous.com/icp1nlc

2020/05/16

VUE全家桶笔记 (VueX&Vue-router)

VUE全家桶笔记 (Vue-router)

Vue-router介绍

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表

  • 模块化的、基于组件的路由配置

  • 路由参数、查询、通配符

  • 基于 Vue.js 过渡系统的视图过渡效果

  • 细粒度的导航控制

  • 带有自动激活的 CSS class 的链接

  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级

  • 自定义的滚动条行为

    引用地址:https://router.vuejs.org/zh/

    说白了就是一个单页应用(SPA)可以通过Vue-router切换到不同的"页面"

起步

要想使用Vue-router就得先安装对应的插件然后将组件映射到路由。

安装

1.在生成项目时选择 Manually select features -> Router

2.在已有的项目中使用 npm i vue-router -S 或者 vue add router

!但是注意 命令安装会造成文件覆盖,安装前一定要提交下项目

在main.js中 引入并且使用 Vue.use()进行注册

import VueRouter from 'vue-router'

Vue.use(VueRouter);

基本使用

src -> router -> index.js

import Vue from "vue";
import VueRouter from "vue-router";  //引入vue-router
import Home from "../views/Home.vue"; //引入组件
import About from "../views/About.vue";
Vue.use(VueRouter); //注册

const routes = [
    {
        path: "/",  //路由地址
        component: Home //跳转的目标组件
    },
    {
        path: "/about",
        component: About,
    },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
    routes
});

export default router; //抛出router

配置完成后可以使用<router-link to="Home">Home</router-link> 创建跳转的链接 to属性相当于a标签的href属性

<router-view />标签作为路由出口渲染组件

命名路由

在配置路由时可以给路由一个name属性进行动态切换

src -> router -> index.js

const routes = [
    {
        path: "/",
        name: "Home",
        component: Home
    },
    {
        path: "/about",
        name: "About",
        component: About,
    },
];

绑定to属性 {name:'路由中对应的home属性值'}

<router-link :to="{name:'Home'}">Home</router-link>

<router-link :to="{name:'About'}">About</router-link>

这里还要注意下单引号和双引号嵌套问题

动态路由匹配&路由组件复用

在网站中 经常有动态路由的情况 例如 https://space.bilibili.com/16052014 这里的16052014就是一个动态路由,每一个不同的动态路由都会渲染相同组件不同的用户信息。

src -> router -> index.js

import Vue from "vue";
import VueRouter from "vue-router";  
import User from "../views/User"; //新创建的用户组件
Vue.use(VueRouter); 

const routes = [
    {
       /*
        /:id就是动态路由匹配 例如http://xxxxx/user/1
        这里的1就是匹配的:id
      */
      path: "/user/:id",  
      name: "User",
      component: User,
    }
];

const router = new VueRouter({
    mode: "history",
    base: process.env.BASE_URL,
    routes
});

export default router; //抛出router

使用params接收匹配路由 因为可能存在多个使用对象进行接收

<router-link :to="{name:'User',params:{id:1}}">用户1</router-link>

<router-link :to="{name:'User',params:{id:2}}">用户2</router-link>

获取id中的内容与后端进行相对应的数据请求展示不同的数据

src -> views -> User.vue

```javascript

$route是一个对象然后取到params字段然后再取到对应的key

**!注意  当路由参数发生变化时 /user/1 切换到  /user/2时 原来的组件实例会被复用,因为两个路由渲染的是同一个组件,复用高效。**

**但是带来的问题是因为组件是被复用的而不是重新进行渲染这里如果使用生命周期钩子函数会造成一些问题**

出现上述问题可以使用watch监听$route:(to,from) to是到哪里去 from是从哪里来 然后就可以愉快的进行一些操作了 例如ajax请求然后改变视图 (to.params.id)

还有一种方法就是利用导航守卫 beforeRouteUpdate

```javascript
beforeRouteUpdate(to,from,next){
  console.Log(to.params.id);
    //拿到对应去哪里的id就可以在这里发ajax请求了
    
    next(); //注意这里一定要调用下next 否则路由会被阻塞
}
</code></pre>
</blockquote>

<h3>404路由和匹配优先级</h3>

<blockquote>
  有这样一个场景用户输入了网站未定义的路由地址 默认Vue-router会跳转到首页,显然这样是不科学的,我们可以定义一个404路由提示用户你输入的地址不存在。
  
  src -> router -> index.js

<pre><code class="language-javascript line-numbers">import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import About from "../views/About.vue";
import User from "../views/User";
Vue.use(VueRouter);

const routes = [
      {
        path: "/",
        name: "Home",
        component: Home
      },
      {
        path: "/about",
        name: "About",
        component: About
      },
      {
        path: "/user/:id",
        name: "User",
        component: User
      },
      {
        path: "*",  // *代表通配 也就是上面的路由没有找到会匹配到这里 自然这里就引申出一个优先级的问题
        name: "404",
        component: () => import("../views/404") // 异步组件加载方式 效率更高
      }
];

const router = new VueRouter({
    mode: "history",
    base: process.env.BASE_URL,
    routes
});

export default router;

</code></pre>
  
  <strong>!注意404路由一定要放到路由配置最下方,因为这里存在一个匹配优先级的问题 他会从上往下找 如果404路由放到首位或者中间某一个位置就会造成下方的路由失效</strong>
  
  src -> views -> User.vue
  
  
  ```javascript
  
*(通配符)在Vue-router中还有一些其他的作用例如在一个后台管理系统

src -> router -> index.js

```javascript
import Vue from "vue";
import VueRouter from "vue-router";

import User from "../views/User";
import UserAdmin from "../views/UserAdmin";
Vue.use(VueRouter);

const routes = [
      {
        path: "/user/:id",
        name: "User",
        component: User
      },
      {
        path: "/user-*",
        name: "UserAdmin",
        component: UserAdmin
      },
];

const router = new VueRouter({
    mode: "history",
    base: process.env.BASE_URL,
    routes
});

export default router;
</code></pre>
  
  如果说是一个这样的路由<code>http://localhost:8080/user/1</code>就是之前定义的用户动态路由如果说是一个这样的路由<code>http://localhost:8080/user-admin</code>他就会切换到一个admin路由下,可以使用<code>$route.params.pathMatch</code>获取到/user-*通配的内容
  
  src -> views -> UserAdmin.vue
  
  
  ```javascript
  
```

路由参数查询

有这样一种路由是长成这种样子http://localhost:8080/page?id=1&title=foo 这种形式的路由这个例子有两个参数 src -> router -> index.js
import Vue from "vue";
import VueRouter from "vue-router";
import Page from "../views/Page";
Vue.use(VueRouter);

const routes = [
  {
    path: "/page",
    name: "Page",
    component: Page
  },
];

const router = new VueRouter({
    mode: "history",
    base: process.env.BASE_URL,
    routes
});

export default router;

src -> App.vue

```html

```

query为参数查询 当然这里的数据是写死的 因为我们使用的是命名路由未来可以替换成动态数据,然后可以在组件中拿到对应的参数进行一些操作。

```javascript

```

路由重定向&别名

src -> router -> index.js

const routes = [
{
 path: "/", 
 redirect:{name:'Home'} // 默认的 / 就会跳转到Home路由 
},
{
 path: "/home",
 name: "Home",
 component: Home
},
];

别名就不多说了 alias: '/aaa'一个属性 一个别名

路由组件传值

在之前我们是使用$route获取地址栏上的值进行传值,但是随着项目的复杂度增加地址栏上的参数会越来越多$route获取值的方式会显得复杂,也会跟路由形成高度的耦合,当前组件只能在特定的URL使用,限制了组件灵活。

解决这个问题可以在定义路由的时候使用porps属性

src -> router -> index.js

{
  path: "/user/:id",
  name: "User",
  component: User,
  props:true //使用props传值
},

src -> views -> User.vue

```javascript

在路由中定义props还有其他的用法例如定义一个函数

src -> router -> index.js

```javascript
{
  path: "/user/:id",
  name: "User",
  component: User,
  props: route => ({
    id: route.params.id,
    title: route.query.title
  })
},
</code></pre>
  
  src -> views -> User.vue
  
  
  ```javascript
  
``` 之后访问http://localhost:8080/user/1?title=lalalala就可以拿到对应的title参数值了

编程式导航

我们可以通过this.$route获取路由组件实例对象里面有一些方法例如push go back等方法进行动态的路由跳转,我们称之为编程形导航。 类似于<router-link :to="{name:'Home'}">Home</router-link> 叫做声明形导航 例如我在User组件下添加了一个返回首页的按钮我们可以这样做 src -> views -> User.vue ```javascript
``` 也可以使用命名的方式例如this.$router.push("name");也可以是个对象this.$router.push({path:'/'}); 也可以是一个命名路由 也可以进行前进或者后退,使用this.$route.to()进行控制。参数1就是前进,参数0就是刷新,参数-1就是后退

嵌套路由

src -> router -> index.js
{
  path: "/user/:id",
  name: "User",
  component: User,
  props: route => ({
    id: route.params.id,
    title: route.query.title
  }),
  children: [
    {
      path: "posts",
      component: () => import("../views/Posts")
    }
  ]
},

然后就是在一个位置定义跳转<router-link to="/user/1/posts">children</router-link>最后在user中定义路由出口 <router-view />

命名视图

一般情况下,我们在路由配置中,一个路由路径只能对应一个组件,若想对应多个组件,必须得作为子组件存在,然后再一个公用的视图内显示,这是一个路由对应多个组件,这些组件对应一个视图(就是上面嵌套路由的玩法)

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。

src -> router -> index.js

const routes = [
   {
     path: "/home",
     name: "Home",
     component:{
         default:Home, //默认的名字
         Sidebar:() => import('../views/Sidebar'),
         Main:() => import('../views/main'),
     }
   },

];

src -> views -> Home.vue

<router-view />
  
  <router-view name="Sidebar"/>
  <router-view name="Home"/>
  

全局守卫

可以使用router.beforEach注册一个全局前置守卫

beforeRouteUpdate(to, from, next) {
  next();
},

有个需求,用户在浏览网站时,会访问很多组件,用户跳转到/notes,发现用户没有登录,此时应该让用户登陆后查看,应该让用户跳转到登录页面,登陆完成后看到我的笔记内容,这个时候全局守卫起到了关键作用。

2020 我在使用VScode时安装的插件(针对前端开花推荐)

针对前端开花的VScode插件推荐

呐,半年前分享过一次插件推荐,但是即使vscode打上很多的插件开发效率仍然无法与一些IDE相比(webstrom phpstrom....)但是随着版本的更新vscode也集成了一些热门的功能而且插件质量也有所提升,最近开发都在使用VScode逐渐的减少使用webstrom。

呐在这里F_picacho向大家分享下我在使用VScode整理出的插件清单供大家参考。 针对上一次的分享内容有删改,因为vscode最新版集成了一些功能所以有的插件可以删除了,留着影响运行效率。


主题类

默认的vscode暗色系非常生硬,开发过程极影响心情。有一款能让自己愉快编码的主题也会在一定程度上提升编程效率和灵感。当然,不同人对于视觉的偏好是不同的。

f821b2a9fc875b31

Material Icon Theme 文件类型添加对应的图标展示
One Dark Pro 一款看着更柔顺的主题
Chinese (Simplified) Language Pack for Visual Studio Code 中文简体语言包


通用类

Prettier – Code formatter 一键Prettier风格 输入一串指令皆可把代码改变成流行的Prettier风格
694bda6fe708c94a

注意这里需要进行对seetings.json文件进行配置 否则可能无法正常使用
ctrl+shift+p进入搜索 搜索seetings.json
插件其启用快捷键

// 默认代码风格配置shift+alt+f 或者直接使用指令Format Selection
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"window.zoomLevel": 0,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},

Auto Complete Tag 修改 html 标签,自动帮你完成尾部闭合标签的同步修改

Path Intellisense 资源文件自动路劲补全

Preview on Web Server 热更新服务器  ctrl+shift+v在视窗内启动预览窗口 ctrl+shift+l在浏览器预览

这里不推荐live server 因为很长时间没有更新了 后期使用还有一些跨域的问题

open in browser 以本地的方式打开文件 支持所有内核浏览器 启用快捷键alt+shift+b

Bracket Pair Colorizer 2 标记代码块方便审计代码

JavaScript (ES6) code snippets 提示追加ES6语法模板


CSS预处理语言类

Easy Sass 编译Sass文件 不需要安装额外的执行环境 但是需要进行配置

    
// Easy sass插件配置
    "easysass.compileAfterSave": true, 
    "easysass.formats": [
           {
               "format": "expanded",
               "extension": ".css"
           },
           {
               "format": "compressed",
               "extension": ".min.css"
           }
       ],
    "easysass.targetDir": "./css/",
    "workbench.iconTheme": "material-icon-theme" //路径

Sass Sass语法提示


VUE类

Vetur Vue语法提示 高亮

Vue VSCode Snippets Vue语法模板快捷输入