焦伟奇

焦伟奇 查看完整档案

南京编辑滁州学院  |  电子信息工程 编辑未知  |  前端小喽喽 编辑 github.com/wqjiao 编辑
编辑

欢迎点击查看,有错烦请指点,给个 star 鼓励下下
https://github.com/wqjiao/sha...

个人动态

焦伟奇 赞了文章 · 2020-09-08

JS错误监控总结

前言

做好错误监控,将用户使用时的错误日志上报,可以帮助我们更快的解决一些问题。目前开源的比较好的前端监控有

那前端监控是怎么实现的呢?要想了解这个,需要知道前端错误大概分为哪些以及如何捕获处理。

前端错误分为JS运行时错误、资源加载错误和接口错误三种。

一、JS运行时错误

JS运行时错误一般使用window.onerror捕获,但是有一种特殊情况就是promise被reject并且错误信息没有被处理的时候抛出的错误

1.1 一般情况的JS运行时错误

使用window.onerror和window.addEventListener('error')捕获。

window.onerror = function (msg, url, lineNo, columnNo, error) 
    { 
       // 处理error信息
    } 
 
    window.addEventListener('error', event =>  
    {  
       console.log('addEventListener error:' + event.target); 
    }, true); 
    // true代表在捕获阶段调用,false代表在冒泡阶段捕获。使用true或false都可以
例子:https://jsbin.com/lujahin/edit?html,console,output 点击button抛出错误,分别被window.onerror和window.addEventListener('error')捕获

1.2 Uncaught (in promise)

当promise被reject并且错误信息没有被处理的时候,会抛出一个unhandledrejection,并且这个错误不会被window.onerror以及window.addEventListener('error')捕获,需要用专门的window.addEventListener('unhandledrejection')捕获处理

window.addEventListener('unhandledrejection', event => 
    { 
       console.log('unhandledrejection:' + event.reason); // 捕获后自定义处理
    });
https://developer.mozilla.org...
例子:https://jsbin.com/jofomob/edit?html,console,output 点击button抛出unhandledrejection错误,并且该错误仅能被window.addEventListener('unhandledrejection')捕获

1.3 console.error

一些特殊情况下,还需要捕获处理console.error,捕获方式就是重写window.console.error

var consoleError = window.console.error; 
window.console.error = function () { 
    alert(JSON.stringify(arguments)); // 自定义处理
    consoleError && consoleError.apply(window, arguments); 
};
例子:https://jsbin.com/pemigew/edit?html,console,output

1.4 特别说明跨域日志

什么是跨域脚本error?

https://developer.mozilla.org...
当加载自不同域的脚本中发生语法错误时,为避免信息泄露(参见bug 363897),语法错误的细节将不会报告,而代之简单的"Script error."。在某些浏览器中,通过在<script>使用crossorigin属性并要求服务器发送适当的 CORS HTTP 响应头,该行为可被覆盖。一个变通方案是单独处理"Script error.",告知错误详情仅能通过浏览器控制台查看,无法通过JavaScript访问。

例子: http://sandbox.runjs.cn/show/... 请打开页面打开控制台。该页面分别加载了两个不同域的js脚本,配置了crossorigin的window.onerror可以报出详细的错误,没有配置crossorigin只能报出'script error',并且没有错误信息

1.5 特别说明sourceMap

在线上由于JS一般都是被压缩或者打包(webpack)过,打包后的文件只有一行,因此报错会出现第一行第5000列出现JS错误,给排查带来困难。sourceMap存储打包前的JS文件和打包后的JS文件之间一个映射关系,可以根据打包后的位置快速解析出对应源文件的位置。

但是出于安全性考虑,线上设置sourceMap会存在不安全的问题,因为网站使用者可以轻易的看到网站源码,此时可以设置.map文件只能通过公司内网访问降低隐患

sourceMap配置devtool: 'inline-source-map'
如果使用了uglifyjs-webpack-plugin 必须把 sourceMap设置为true
https://doc.webpack-china.org...

1.6 其它

1.6.1 sentry把所有的回调函数使用try catch封装一层
https://github.com/getsentry/raven-js/blob/master/src/raven.js

1.6.2 vue errorHandler
https://vuejs.org/v2/api/#errorHandler
其原理也是使用try catch封装了nextTick,$emit, watch,data等
https://github.com/vuejs/vue/blob/dev/dist/vue.runtime.js

二、资源加载错误

使用window.addEventListener('error')捕获,window.onerror捕获不到资源加载错误

https://jsbin.com/rigasek/edit?html,console 图片资源加载错误。此时只有window.addEventListener('error')可以捕获到

window.onerror和window.addEventListener('error')的异同:相同点是都可以捕获到window上的js运行时错误。区别是1.捕获到的错误参数不同 2.window.addEventListener('error')可以捕获资源加载错误,但是window.onerror不能捕获到资源加载错误

window.addEventListener('error')捕获到的错误,可以通过target?.src || target?.href区分是资源加载错误还是js运行时错误

三、接口错误

所有http请求都是基于xmlHttpRequest或者fetch封装的。所以要捕获全局的接口错误,方法就是封装xmlHttpRequest或者fetch

3.1 封装xmlHttpRequest

if(!window.XMLHttpRequest) return;
var xmlhttp = window.XMLHttpRequest;
var _oldSend = xmlhttp.prototype.send;
var _handleEvent = function (event) {
    if (event && event.currentTarget && event.currentTarget.status !== 200) {
          // 自定义错误上报 }
}
xmlhttp.prototype.send = function () {
    if (this['addEventListener']) {
        this['addEventListener']('error', _handleEvent);
        this['addEventListener']('load', _handleEvent);
        this['addEventListener']('abort', _handleEvent);
    } else {
        var _oldStateChange = this['onreadystatechange'];
        this['onreadystatechange'] = function (event) {
            if (this.readyState === 4) {
                _handleEvent(event);
            }
            _oldStateChange && _oldStateChange.apply(this, arguments);
        };
    }
    return _oldSend.apply(this, arguments);
}

3.2 封装fetch

if(!window.fetch) return;
    let _oldFetch = window.fetch;
    window.fetch = function () {
        return _oldFetch.apply(this, arguments)
        .then(res => {
            if (!res.ok) { // True if status is HTTP 2xx
                // 上报错误
            }
            return res;
        })
        .catch(error => {
            // 上报错误
            throw error;  
        })
}

结论

  1. 使用window.onerror捕获JS运行时错误
  2. 使用window.addEventListener('unhandledrejection')捕获未处理的promise reject错误
  3. 重写console.error捕获console.error错误
  4. 在跨域脚本上配置crossorigin="anonymous"捕获跨域脚本错误
  5. window.addEventListener('error')捕获资源加载错误。因为它也能捕获js运行时错误,为避免重复上报js运行时错误,此时只有event.srcElement inatanceof HTMLScriptElement或HTMLLinkElement或HTMLImageElement时才上报
  6. 重写window.XMLHttpRequest和window.fetch捕获请求错误

利用以上原理,简单写了一个JS监控,只处理了一些JS错误,暂时没有做和性能相关的监控
https://github.com/Lie8466/better-js

如果发现文章有错误,欢迎指正。

查看原文

赞 55 收藏 40 评论 6

焦伟奇 发布了文章 · 2020-07-30

老生常谈之跨域

一、JSONP

  • <script data-original=""></script>

基本原理就是通过动态创建 script 标签,然后利用 src 属性进行跨域(后端用回调函数名称包裹数据进行返回即可),但是要注意 JSONP 只支持 GET 请求,不支持 POST 请求:

// 回调函数
function showData (result) {
    // json 对象转成字符串
    $('#text').val(JSON.stringify(result));
}
$(document).ready(function () {
    $('#btn').click(function () {
        //向头部输入一个脚本,该脚本发起一个跨域请求
        $('head').append('<script data-original="http://localhost:9090/student?callback=showData"><\/script>');
    });
});
  • jQueryJSONP 请求
$(document).ready(function () {
    $('#btn').click(function () {
        $.ajax({
            url: 'http://localhost:9090/student',
            type: 'GET',
            dataType: 'jsonp', // 指定服务器返回的数据类型
            jsonpCallback: 'showData',  // 也可以指定回调函数
            success: function (data) {
                // json对象转成字符串
                $('#text').val(JSON.stringify(data));
            }
        });
    });
});

二、CORS 跨域资源共享

利用 nginx 或者 phpjava 等后端语言设置允许跨域请求:

header('Access-Control-Allow-Origin: *'); // 允许所有来源访问
header('Access-Control-Allow-Method: POST,GET'); // 允许访问的方式

三、服务器代理

浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。

  • Nodejs 做代理(eggjs)
async demo() {
    const { ctx: {inputs} } = this;
    // 第三方接口地址
    const url = 'http://api.map.baidu.com/location/ip';
    // 获取第三方接口
    const res = await this.ctx.curl(url, {
        method: 'POST',
        dataType: 'text',
        data: inputs
    });
    // 返回数据
    this.success({
        data: res.data~~~~
    });
}

四、Nginx 反向代理

在配置文件 nginx.conf 中添加以下配置:

location / {  
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

更多文章

查看原文

赞 0 收藏 0 评论 0

焦伟奇 赞了文章 · 2020-06-08

如何使用JavaScript访问设备前后摄像头

banner.png
在这篇文章中,我将向您展示如何通过JavaScript在网页上访问设备的摄像头,并支持多种浏览器,而无需外部库。

如何使用相机API

要访问用户的相机(或麦克风),我们使用JavaScript MediaStream API。该API允许通过流访问这些设备捕获的视频和音频。

第一步是检查浏览器是否支持此API:

if (
  "mediaDevices" in navigator &&
  "getUserMedia" in navigator.mediaDevices
) {
  // ok, 浏览器支持它
}

在现代浏览器中,支持是不错的(当然没有Internet Explorer)。

捕获视频流

要捕获由摄像机生成的视频流,我们使用 mediaDevices 对象的 getUserMedia 方法。这个方法接收一个对象,其中包含我们要请求的媒体类型(视频或音频)和一些要求。首先,我们可以通过 {video: true} 来获取摄像机的视频。

const videoStream = await navigator.mediaDevices.getUserMedia({ video: true });

此调用将询问用户是否允许访问摄像机。如果用户拒绝,它将引发异常并且不返回流。因此,必须在 try/catch 块内完成处理这种情况。

请注意,它返回一个Promise,因此您必须使用 async/awaitthen 块。在Mac OS系统上还会弹出授权

点击“好”,就可以访问电脑摄像头了,控制台输出的 videoStream 对象如下

视频规格(requirements)

我们可以通过传递有关所需分辨率以及最小和最大限制的信息来改善视频的要求:

const constraints = {
  video: {
    width: {
      min: 1280,
      ideal: 1920,
      max: 2560,
    },
    height: {
      min: 720,
      ideal: 1080,
      max: 1440,
    },
  },
};

const videoStream = await navigator.mediaDevices.getUserMedia(constraints);

这样,流以正确的宽度和高度比例进入,如果它是处于纵向模式的手机,则需要进行尺寸反转。

在页面上显示视频

既然有了流,我们该如何处理?

我们可以在页面上的 video 元素中显示视频:

// 页面中有一个 <video autoplay id="video"></video> 标签
const video = document.querySelector("#video");
const videoStream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = videoStream;

请注意 video 标签中的自动播放属性 autoplay,没有它,你需要调用 video.play() 才能真正开始显示图像。

访问手机的前后摄像头

默认情况下,getUserMedia 将使用系统默认的视频录制设备。如果是有两个摄像头的手机,它使用前置摄像头。

要访问后置摄像头,我们必须在视频规格中包括 faceModeMode:"environment"

const constraints = {
  video: {
    width: { ... },
    height: { ... },
    facingMode: "environment"
  },
};

默认值为 faceingMode:"user",即前置摄像头。

需要注意的是,如果你想在已经播放视频的情况下更换摄像机,你需要先停止当前的视频流,然后再将其替换成另一台摄像机的视频流。

videoStream.getTracks().forEach((track) => {
  track.stop();
});

截屏

你可以做的另一件很酷的事情是捕获视频的图像(屏幕快照)。

你可以在canvas上绘制当前视频帧,例如:

// 页面中有一个 <canvas id="canvas"></canvas> 标签
const canvas = document.querySelector("#canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext("2d").drawImage(video, 0, 0);

你还可以在 img 元素中显示画布内容。

在本教程创建的示例中,我添加了一个按钮,该按钮可从画布动态创建图像并将其添加到页面:

const img = document.createElement("img");
img.src = canvas.toDataURL("image/png");
screenshotsContainer.prepend(img);

完整示例和代码

在线效果及源代码:https://coding.zhangbing.site...

PC

手机QQ中浏览效果


本文首发于公众号《前端全栈开发者》ID:by-zhangbing-dev,第一时间阅读最新文章,会优先两天发表新文章。关注后私信回复:大礼包,送某网精品视频课程网盘资料,准能为你节省不少钱!
subscribe2.png

查看原文

赞 32 收藏 28 评论 11

焦伟奇 赞了文章 · 2020-04-11

nodejs使用mongoose 获取mongodb 数据格式化问题

说明


在mongodb 中获取数据,不管使用回调函数还是Promise又或者generate 语法最后得到都是mongoose.Query对象,不能直接操作如同普通对象一样得到单行数据。还有就是时间格式化问题,见下面。

下面示例默认使用co 和 es6 */yield 语法,当然也可以使用async/await

1. 安装mongoose


$ npm install mongoose co moment --save

说明:
mongoose: mongodb 的nodejs 数据库驱动
co: 一个自动执行的generate函数容器
moment: 时间格式化

2. 处理数据


如下代码:

// 省略链接数据库,详情见文档
const mongoose = require('mongoose')
const co = require('co')
const Schema = mongoose.Schema
const PersonSchema = new Schema({
    name: String,
    age: String,
    createAt: {
        type: Date,
        default: Date.now,
        get: v => moment(v).format('YYYY-MM-DD HH:mm')
    }
})

const PersonModel = mongoose.model('Person', PersonSchema) // 创建模型
const Person = new PersonModel() // 创建文档对象
co(function * () {
    let person = yield Person.findOne({name: 'zhengsan'})
    // 这里person 是一个mongoose.Query对象
    // 到这里一般直接使用person 就是一个对象能够获取{ name, type, createAt } 
    // 但是不能够操作这三个,除非单独一个一个复制到另外一个对象,问题就在这里?
})()

这里通过 Object.keys(person)可以得到如下数据:

[ '$__', 'isNew', 'errors', '_doc', '$init' ]

3. 得到普通对象


通过查询mongoose 文档最后得到解决办法而且,可以轻松格式化哦:

// 接上面代码
let person = yield Person.findOne({name: 'zhangsan'})
person = person.toJSON({getters: true})
// 此时person对象对象
// { name: 'zhengsan', age: 32, createAt: '2017-02-03 12:30' }

文章不对之处,欢迎指正...

查看原文

赞 1 收藏 1 评论 0

焦伟奇 关注了用户 · 2020-02-17

疯狂的技术宅 @evilboy

资深技术宅,爱好广泛,兴趣多变。博览群书,喜欢扯淡。十八种语言样样稀松。想要了解更多,请关注微信公众号:充实的脑洞

关注 5902

焦伟奇 赞了文章 · 2020-02-17

Svelte 3 快速开发指南(对比React)

翻译:疯狂的技术宅

原文:https://www.valentinog.com/bl...

未经允许严禁转载

什么是Svelte?

Svelte 是由 Rich Harris 创建的 JavaScript UI 库。 Rich 认为 virtual DOM 带来了额外开销,并提出了 Svelte,现在它正处于第三版的状态。

但是你为什么要学习Svelte?而不是 React 或 Vue?嗯,它有一些有趣的卖点:

  • Svelte是编译器,而不是像 React 或 Vue 这样的依赖项
  • Svelte 似乎需要更少的代码,用 React 开发同样的功能代码量大约会多 40% (来源:Rich Harris)
  • Svelte 没有 virtual DOM,它会被编译成最小的 “vanilla” JavaScript,并且看起来比其他库性能更好

在下面的教程中,我更关注 Svelte 3 的核心概念。

不管怎样,不要过分的去追逐潮流。 Svelte 3 确实很有趣,虽然它在一些细节上还比较粗糙。你可以通过本教程来试试 Svelte 3 的水到底有多深,并形成你自己的观点

请慢慢享用。

本指南适用于哪些人(要求)

如果你对以下内容有基本的了解,那么学习本教程就没有问题:

  • HTML、CSS 和 JavaScript (ES6+)
  • import 和 export 语法(ES6模块)
  • async/await 语法
  • 组件等概念
  • fetch API

如果你是前端初学者,那么这个教程对你来说也许太过分了。但是不要绝望,先学习以下资源然后再回来。

如果你需要学习 ES6模块,请查看 JavaScript 中关于 importexport 语句的文档。还有优秀的文章 ES6 Modules in depth

要了解有关 Fetch API 的更多信息,请查看 Fetch API

(是的,对于初学者来说,要学的东西是很多。但不是我的错!)。

最后还要确保在系统上安装了较新版本的 Node.js.

你将学习到的内容

我们不会在本教程中构建一个 “全栈的” 程序。相反,我将通过构建一些小的 UI 来引导你完成 Svelte 3 的核心概念。最后,你应该能够开始使用 Svelte 进行构建,并了解了如何创建组件以及如何处理事件等等。

现在享受学习 Svelte 的乐趣!

设置项目

与所有现代 JavaScript 项目一样,我们需要完成设置项目所有必需的流程。如果要为项目创建 Git 仓库,请先完成这一步,然后在本地计算机上克隆仓库。

克隆后,你应该已准备好使用 degit 创建一个新的 Svelte 项目了。不用担心,这不是另一个需要学习的工具! Degit 是“愚蠢的”。它只是用来制作 Git repos 的副本,在我们的例子中,我们将把 Svelte 模板克隆到一个新文件夹中(或者在你的Git repo中)。

回顾一下,如果需要,可以创建一个新的Git仓库,然后在本地机器上克隆它:

git clone git@github.com:yourusername/svelte-tutorial.git

然后用 degit 在新文件夹中创建一个新的 Svelte 项目。如果文件夹不是空的,degit 会报错,所以你需要加上强制标志:

npx degit sveltejs/template svelte-tutorial --force

接下来进入新项目并安装依赖项:

cd svelte-tutorial && npm i

现在你应该很高兴的上路了!

了解 Svelte 项目

项目就绪后,先来看看里面都有些什么。使用文本编辑器打开项目。你会看到一堆文件:

  • App.svelte:程序的根组件
  • rollup.config.js:Rollup 的配置,即 Svelte 选择的模块捆绑器

现在打开App.svelte并查看:

<script>
    export let name;
</script>
<style>
    h1 {
        color: purple;
    }
</style>
<h1>Hello {name}!</h1>

这是一个 Svelte 组件!真的,它需要的只是一个脚本标签、一个样式标签和一些 HTML。 name 是一个变量,然后在 HTML 中的花括号之间插入并使用。现在不要过分关注 export 声明,稍后会看到它的作用。

用 Svelte 获取数据

为了开始探索 Svelte,我们将立即开始用重火力进攻:先从 API 中获取一些数据。

就此而言,Svelte 与 React 没有什么不同:它使用名为 onMount 的方法。这是一个所谓的生命周期函数。很容易猜到 Svelte 从哪里借用了这个想法:React 生命周期方法

现在让我们在 src 文件夹中创建一个名为 Fetch.svelte 的新 Svelte 组件。我们的组件从 Svelte 导入 onMount 并向 API 发出获取请求。 onMount 接受回调,并从该回调中发出请求。数据保存在 onMount 内名为 data 的变量中:

<script>
  import { onMount } from "svelte";
  let data = [];
  onMount(async function() {
    const response = await fetch("https://academy.valentinog.com/api/link/");
    const json = await response.json();
    data = json;
  });
</script>

现在打开 App.svelte 并导入新创建的组件(除了 script 标记,你可以删除所有内容):

<script>
  import Fetch from "./Fetch.svelte";
</script>
<Fetch />

正如你所看到的,自定义组件的语法让人想起 React 的 JSX。因为目前组件只是进行 API 调用,还不会显示任何内容。接下来让我们添加更多东西。

用“each”创建列表

在 React 中,我们已经习惯了创建元素列表的映射功能。在 Svelte 中有一个名为“each”的块,我们要用它来创建一个链接列表。 API 返回一个对象数组,每个对象都有一个标题和一个 url。现在要添加一个“each”块:

<script>
  import { onMount } from "svelte";
  let data = [];
  onMount(async function() {
    const response = await fetch("https://academy.valentinog.com/api/link/");
    const json = await response.json();
    data = json;
  });
</script>
{#each data as link}
// do stuff //
{/each}

注意“each”是如何生成变量 data 的,我将每个元素提取为 “link”。要生成元素列表,只需确保将每个元素包装在一个 ul 元素中:

<script>
  import { onMount } from "svelte";
  let data = [];
  onMount(async function() {
    const response = await fetch("https://academy.valentinog.com/api/link/");
    const json = await response.json();
    data = json;
  });
</script>
<ul>
  {#each data as link}
    <li>
      <a href={link.url}>{link.title}</a>
    </li>
  {/each}
</ul>

现在转到你的终端,进入项目文件夹并运行:

npm run dev

访问 http://localhost:5000/ ,你应该看到一个链接列表:

clipboard.png

很好!你学会了如何在 Svelte 中生成元素列表。接下来让我们的组件可以重复使用

传递 props

重用UI组件的能力是这些现代 JavaScript 库的“存在理由”。例如在 React 中有 props、自定义属性(甚至函数或其他组件),我们可以把它们传递给自己的组件,使它们更灵活。

现在 Fetch.svelte 不是可重用的,因为 url 是硬编码的。但不必担心,Svelte 组件也可以从外面接收props。让首先将 url 变为一个变量(我将向你展示组件的相关部分):

<script>
  import { onMount } from "svelte";
  let url = "https://academy.valentinog.com/api/link/";
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

有一个技巧可以使 url 成为 props:只需在变量前加上 export

<script>
  import { onMount } from "svelte";
  // export the variable to make a prop
  export let url = "https://academy.valentinog.com/api/link/";
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

现在打开 App.svelte 并通过传递 url prop 来更新 Fetch 组件:

<script>
  import Fetch from "./Fetch.svelte";
</script>
<Fetch url="https://jsonplaceholder.typicode.com/todos" />

现在,你的组件调用的是新端点而不是默认 URL。另一个好处是标记为 props 的变量可能具有默认值。在我们的例子中,“https://academy.valentinog.co...”是默认 props,作为没有 props 传递时的后备。

现在看看当我们需要不止一个 props 时会发生什么。

多 props 及传播

当然,Svelte 组件可能有多个 props。让我们为组件添加另一个名为 title 的 props:

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  export let title = "A list of links";
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>
<h1>{title}</h1>
<ul>
  {#each data as link}
    <li>
      <a href={link.url}>{link.title}</a>
    </li>
  {/each}
</ul>

再次从 App.svelte 传递新的 props:

<script>
  import Fetch from "./Fetch.svelte";
</script>
<Fetch
  url="https://jsonplaceholder.typicode.com/todos"
  title="A list of todos" />

当 props 开始增多时,你会发现上述方法不切实际。幸运的是,有一种方法可以传播 props。将 props 声明为对象并将它们分布在组件上:

<script>
  import Fetch from "./Fetch.svelte";
  const props = {
    url: "https://jsonplaceholder.typicode.com/todos",
    title: "A list of todos"
  };
</script>
<Fetch {...props} />

很不错不是吗?但我仍然不满意。我想让 Fetch 组件更加可重用,该怎么办?

子组件和“渲染” props

Fetch 这个命名对于组件来说并不差劲,如果它是一个 HTML 列表的话。有一种方法可以从外面传递该列表,就像React 中的子 props 一样。在 Svelte,我们将子组件称为插槽(slot)

第一步,我将从 Fetch.svelte 中删除所有标记,将其替换为插槽,使它摆脱 prop 的“title”:

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>
<slot />

接下来,可以将子元素从外部传递给 Fetch,这就发生在 App.svelte 中:

<script>
  import Fetch from "./Fetch.svelte";
  const props = {
    url: "https://jsonplaceholder.typicode.com/todos"
  };
</script>
<Fetch {...props}>
  <h1>A list of todos</h1>
  <ul>
    <li>now what?</li>
  </ul>
</Fetch>

但现在我们遇到了问题。我需要data,它存在于 Fetch.svelte 中,这点很重要,因为我不想手动去创建列表。

在 React 中你可以找到一个 HOC、渲染 propshooks。换句话说,我想渲染一个子组件,但是子组件应该从父组件获取 data

在 Svelte 中,你可以通过将值反向传递给父组件来获得相同的结果。首先将 data 作为 prop 传递给你的插槽:

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  export let title = "A list of links";
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>
<!-- {data} is a shortand for data={data} -->
<slot {data} />

从外面你可以使用符号 let:data={data} 访问数据,这里简写为 let:data

<script>
  import Fetch from "./Fetch.svelte";
  const props = {
    url: "https://jsonplaceholder.typicode.com/todos"
  };
</script>
<!-- let:data is like forwarding a component's data one level upward -->
<Fetch {...props} let:data>
  <h1>A list of todos</h1>
  <ul>
    {#each data as link}
      <li>{link.title}</li>
    {/each}
  </ul>
</Fetch>

现在可以使用来自 Fetch 组件的数据了,它可用于我的每个块。这就像将组件的内部数据向上转发一级

虽然起初可能是反直觉的,但这似乎是一种简洁的方法。你怎么看?在下一节中,我们将介绍 Svelte 中的事件处理。

处理事件和事件修饰符

我们将构建一个表单组件来说明 Svelte 如何处理事件。创建一个名为 Form.svelte 的新文件。现在它包含用于搜索的 input 和提交类型的 button

<script>
</script>
<form>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

(作为练习,你可以将每个元素提取到其自己的组件中)。

然后在 App.svelte 中包含新组件:

<script>
  import Form from "./Form.svelte";
</script>
<Form />

现用程序应该可以在浏览器中渲染你的表单了。此时如果你尝试提交表单,默认行为是:浏览器触发刷新。

要控制 “vanilla” 中的表单,我会为 submit 事件注册一个事件监听器。然后在处理 handler 内部阻止使用 event.preventDefault() 的默认值:

// vanilla JS example
var form = document.getElementsByTagName('form')[0]
form.addEventListener('submit', function(event){
    event.preventDefault();
});

在 Svelte 组件内部情况有所不同:使用“on”注册事件handler,后面分别使用事件名称和处理函数:

<script>
  function handleSubmit(event) {
    // do stuff
  }
</script>
<form on:submit={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

此外在 Svelte 中有事件修饰符。其中最重要的是:

  • preventDefault
  • stopPropagation
  • once

可以在事件名称之后使用修饰符 preventDefault停用表单上的默认

<script>
  function handleSubmit(event) {
    // do stuff
  }
</script>
<form on:submit|preventDefault={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

还可以将 handleSubmit 作为 prop 来传递,以便使组件更加灵活。这是一个例子:

<script>
  export let handleSubmit = function(event) {
    // default prop
  };
</script>
<form on:submit|preventDefault={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

而已。现在把这个简单的程序更进一步:我想过滤链接列表。表单已经到位但我们需要将 Fetch.svelteForm.svelte 连接起来。我们开始做吧!

快速回顾

让我们回顾一下到目前为止所做的事情。我们有两个组件,Fetch.svelte

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>
<slot {data} />

Form.svelte

<script>
  export let handleSubmit = function(event) {
    // default prop
  };
</script>
<form on:submit|preventDefault={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

App.svelte 是根组件。为方便起见,让我们在 App 中渲染 Form 和 Fetch:

<script>
  import Fetch from "./Fetch.svelte";
  import Form from "./Form.svelte";
</script>
<Form />
<Fetch let:data>
  <h1>A list of links</h1>
  <ul>
    {#each data as link}
      <li>
        <a href={link.url}>{link.title}</a>
      </li>
    {/each}
  </ul>
</Fetch>

Fetch.svelte 从 API 获取数据并向上转发数据。因此当使用块作为插槽时,可以将数据传递给它的子节点。

现在我希望用户根据他在表单中输入的搜索词来过滤数据。看起来像 Form 和 Fetch 需要沟通。让我们看看如何实现这一点。

实现搜索功能

我们需要一个搜索项来过滤数据数组。搜索词可以是从外部传递给 Fetch.svelte 的 props。打开 Fetch.svelte 并添加新的 prop searchTerm:

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  // new prop
  export let searchTerm = undefined;
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>
<slot {data} />

(searchTerm 被指定为 undefined,以防止 Svelte 对我抱怨 “Fetch 在创建时找不到预期的 prop searchTerm”)。

接下来需要一个新变量来保存 json 响应,因为我们将根据 searchTerm 过滤该响应。添加一个名为 jsonResponse 的新变量,使用 jsonResponse 来存储 API 的响应而不是将 json 保存到数据:

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  // new prop
  export let searchTerm;
  // new variable
  let jsonResponse = [];
  let data = [];
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    // save the response in the new variable
    jsonResponse = json;
  });
</script>
<slot {data} />

此时变量数据将包含:

  • 如果没有提供 searchTerm,则为原始 jsonResponse
  • 如果 searchTerm 不为空,则为过滤后的数组

对于过滤数组元素,我们可以基于 RegExp 对照标题属性进行匹配。 (API返回一个对象数组。每个对象都有 title 和 url)。第一个实现可能是:

 const regex = new RegExp(searchTerm, "gi");
  const data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

说得通!让我们看看完整的组件:

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  // new prop
  export let searchTerm = undefined;
  // new variable
  let jsonResponse = [];
  const regex = new RegExp(searchTerm, "gi");
  const data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    // save the response in the new variable
    jsonResponse = json;
  });
</script>
<slot {data} />

在这一点上,我们需要对 App.svelte 进行一些调整。 searchTerm 应该是来自外部的动态 props。然后我们在用户提交表单时拦截输入的值。打开 App.svelte 并将 searchTerm 作为 Fetch 的 prop 传递:

<script>
  import Fetch from "./Fetch.svelte";
  import Form from "./Form.svelte";
  let searchTerm;
</script>
<Form />
<Fetch {searchTerm} let:data>
  <h1>A list of links</h1>
  <ul>
    {#each data as link}
      <li>
        <a href={link.url}>{link.title}</a>
      </li>
    {/each}
  </ul>
</Fetch>

接下来我们创建并传递 handleSubmit 作为 Form 的 prop,并在 App.svelte 内部保存用户在变量 searchTerm 中输入的搜索词:

<script>
  import Fetch from "./Fetch.svelte";
  import Form from "./Form.svelte";
  let searchTerm;
  function handleSubmit() {
    const { value } = this.elements.search;
    searchTerm = value;
  }
</script>
<Form {handleSubmit} />
<Fetch {searchTerm} let:data>
  <h1>A list of links</h1>
  <ul>
    {#each data as link}
      <li>
        <a href={link.url}>{link.title}</a>
      </li>
    {/each}
  </ul>
</Fetch>

几乎完成了。保存所有文件并运行开发服务器。你会看到......一个空白的页面!

clipboard.png

这是怎么回事?赶快进入下一节!

反应式编程

Svelte 处理计算值的方式可能一开始看起来不直观。我们的问题在于 Fetch.svelte,它来自以下几行:

 const regex = new RegExp(searchTerm, "gi");
  const data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

思考一下,假设我们有两个值, regex 取决于 searchTerm,我们希望每次后者更改时要重新生成前者。

然后我们有数据:它应该每次重新处理 searchTerm正则表达式。就像电子表格一样:一个值可能取决于其他值

Svelte 从“反应式编程”中汲取灵感,并对所谓的计算值使用奇怪的语法。这些值在 Svelte 3 中被称为“反应声明”。下面是应该如何调整上述代码:

 $: regex = new RegExp(searchTerm, "gi");
  $: data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

$:不是外来的语法。它只是简单的 JavaScript,它被称为标签声明

这里是完整的 Fetch.svelte

<script>
  import { onMount } from "svelte";
  export let url = "https://academy.valentinog.com/api/link/";
  export let searchTerm = undefined;
  let jsonResponse = [];
  $: regex = new RegExp(searchTerm, "gi");
  $: data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    jsonResponse = json;
  });
</script>
<slot {data} />

现在,搜索功能将像魔法一样工作:

clipboard.png

(过滤 API 级别的链接比每次获取所有链接更好)。

如果你想知道如何用 React实现相同的“app”,请看下一部分。

与 React 的对比

用 React 构建的相同功能的 demo 看起来是怎样的呢?这是 App.js,相当于 App.svelte

import React, { useState } from "react";
import Fetch from "./Fetch";
import Form from "./Form";
function App() {
  const [searchTerm, setSearchTerm] = useState("");
  const fetchProps = {
    url: "https://academy.valentinog.com/api/link/",
    searchTerm
  };
  function handleSubmit(event) {
    event.preventDefault();
    const { value } = event.target.elements.search;
    setSearchTerm(value);
  }
  return (
    <>
      <Form handleSubmit={handleSubmit} />
      <Fetch
        {...fetchProps}
        render={links => {
          return (
            <>
              <h1>A list of links</h1>
              <ul>
                {links.map(link => (
                  <li key={link.url}>
                    <a href={link.url}>{link.title}</a>
                  </li>
                ))}
              </ul>
            </>
          );
        }}
      />
    </>
  );
}
export default App;

这里我使用带有渲染 props 的 Fetch 组件。我可以使用 hook,但我想告诉你同样的概念如何适用于 Svelte 和React。

换一种说法:

  • 对于从React 中的子组件访问父组件的状态,你可以使用 render props(或用于共享数据获取的自定义hook)
  • 对于从 Svelte 插槽访问父组件的状态,你可以从父节点向上转发

如果你将 App.jsSvelte 对应代码(点击这里)进行比较,可以看到典型的 Svelte 组件比 React 等效组件更加简洁

通过在 Svelte 3 中的事实很容易解释,不需要显式调用 setSomeState 或类似的函数。 仅通过为变量赋值,Svelte 就能“做出反应”

接下来是 Form.jsForm.svelte 的 React 实现:

import React from "react";
function Form(props) {
  return (
    <form onSubmit={props.handleSubmit}>
      <label htmlFor="search">Search:</label>
      <input type="search" id="search" required={true} />
      <button type="submit">Search</button>
    </form>
  );
}
export default Form;

没有什么可看的,只是一个函数接受一些 props。

最后是 Fetch.js,复制 Fetch.svelte 的功能:

import { useState, useEffect } from "react";
function Fetch(props) {
  const { url, searchTerm } = props;
  const [links, setLinks] = useState([]);
  const regex = new RegExp(searchTerm, "gi");
  const data = searchTerm
    ? links.filter(link => link.title.match(regex))
    : links;
  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(json => setLinks(json));
  }, [url]);
  return props.render(data);
}
Fetch.defaultProps = {
  url: "https://academy.valentinog.com/api/link/"
};
export default Fetch;

上面的组件使用 hook 和渲染 props:再次强调这是不必要的,因为你可以提取 自定义 hook。这里是 Fetch.svelte

<script>
  import { onMount } from "svelte";
  export let url = "fillThis";
  export let searchTerm = undefined;
  let jsonResponse = [];
  $: regex = new RegExp(searchTerm, "gi");
  $: data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;
  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    jsonResponse = json;
  });
</script>
<slot {data} />

他们看起来和我一样帅😀。然而,这些例子远远达不到一个真正的大程序的地步

Svelte 与 React 和 Vue 相比是怎样的?

我被问到与 React 和 Vue 相比,对 Svelte 的看法是什么?我不能评价 Vue,因为我没有太多的使用经验,但我可以看到 Svelte 如何向其借鉴的。

说到 React,Svelte 对我来说很合理,看起来更直观。在粗略的一瞥中,Svelte 3 似乎只是另一种做事方式,也许比 React 更聪明。

在 Svelte 中真正吸引人的是,它与 React 和 Vue 不同,没有 virtual DOM。换句话说,库和实际的文档对象模型之间没有抽象:Svelte 3 可被编译为可能的最小原生 JavaScript。如果你在受限制的环境中运行程序,这将非常有用。

回顾一下,Svelte 是一个非常有趣的库,但至少在文档、生态系统和工具将逐渐成熟之前我会给它更多的时间。

资源

为了解更多关于 Svelte 的信息,我不能只推荐官方文档例子

本教程的源代码在这里

另外请务必去看一看 Svelte 作者的演讲:https://www.youtube.com/embed...

总结

还能做些什么?如果你愿意,Svelte 3 还有很多要学的东西。开箱即用的好东西太多了:

  • scoped styles
  • 双向绑定
  • 状态管理
  • 内置动画

说再见之前,我还要再啰嗦几句。

JavaScript是残酷的。各种库来去匆匆,总会有新的东西需要学习。多年来,我学会了不要过于依赖任何特定的 JavaScript 库,但说实话,我真的很喜欢 React 和 Redux。

React 为大家带来了“组件”,另一方面,库本身需要具有高度专业化的知识才能掌握。相比之下,Vue 更适合初学者,但不幸的是它并不像 React 那样被视为“时尚”(无论那意味着什么)。

Svelte 3 充分利用了两个世界:Svelte 组件看起来像 Vue,而 React 的一些概念也同样适用。

Svelte 比 React 更直观,特别是当一个初学者在 hook 时代去接触 React 时。当然,React 不会很快消失,但我很期待看到 Svelte 的未来。

最后我仍然要老生常谈:要持续不断的学习


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:


查看原文

赞 22 收藏 15 评论 2

焦伟奇 关注了用户 · 2020-02-11

刘小夕 @liuyan666

本人微信公众号: 前端宇宙

写文不易,Star支持一下?

【github】https://github.com/YvetteLau/...

关注 1864

焦伟奇 赞了文章 · 2020-02-11

9个项目助你在2020年成为前端大师!

原文链接:https://dev.to/simonholdorf/9...

DEV的年度热文,读完觉得不错,所以翻译出来供大家参考,个人水平有限,文中可能会有一些翻译错误,可以在评论区指正。

本篇文章一共涉及了9个流行的框架/库,没有具体的介绍使用方法,而是给了一些非常棒的实战教程。

初学者(也许一些有经验的开发者也是一样)在读完官方文档,想写一个项目练手的时候不知道做什么项目好,或是有想法,但是无从下手。那么这篇文章将会给你带来很大的帮助。

更多文章可戳:https://github.com/YvetteLau/...

导读

无论你是编程新手还是经验丰富的开发人员。在这个行业中,我们不得不一直学习新概念和新语言或是框架,才能跟上快速变化。以React为例 —— FaceBook 四年前开源,现在它已经成为了全球JS开发者的首选。但是与此同时,Vue 和 Angular 也有自己的追求者。然后是 Svelte,Next 和 Nuxt.js,Gatsby,Gridsome,quasar 等等,如果你想成为专业的 JavaScript 开发人员,你在使用自己熟悉的框架进行开发的同时,还需要对不同的框架和库有一些了解。

为了帮助你在2020年成为一个前端大神,我收集了9个使用了不同JS框架/库的项目,你可以去构建或者将他们加入到自己未来的开发计划中。记住,没什么比实际开发一个项目更有帮助。所以,不要犹豫,试着去开发一下。

1. 使用React(with hooks)构建一个电影搜索应用

首先,你可以使用React构建一个电影搜索应用。展示如下:

k1.jpeg

你将学到什么?

构建这个项目,你可以使用较新的 Hook API 来提升你的 React 技能。示例项目使用了React组件,很多 hooks 以及一些外部的 API,当然还有一些CSS样式。

技术栈/点

  1. React(Hooks)
  2. create-react-app
  3. JSX
  4. CSS

你可以在这里看到这个示例项目:https://www.freecodecamp.org/...

2.使用Vue构建一个聊天应用

另外一个要介绍给你的很棒的项目是使用Vue构建的聊天应用程序。展示如下:

👀.png

你将学到什么?

您将学习到如何从头开始设置Vue应用,创建组件,处理状态,创建路由,连接到第三方服务,甚至是处理身份验证。

技术栈/点

  1. Vue
  2. Vuex
  3. Vue Router
  4. Vue CLI
  5. Pusher
  6. CSS

这真的是一个非常棒的项目,不管是用来学习Vue或者是提升现有的技能,以应对2020年的发展。你可以查看这个教程: https://www.sitepoint.com/pus...

3. 使用Augular8构建一款漂亮的天气应用

此示例将帮助你使用 Google 的 Angular 8 来构建一块漂亮的天气应用程序:

k3.png

你将学到什么?

该项目将教你一些宝贵的技能,例如从头开始创建应用,从设计到开发,一直到生产就绪部署。

技术栈/点

  1. Angular 8
  2. Firebase
  3. SSR
  4. 网络布局和Flexbox
  5. 移动端友好 && 响应式布局
  6. 深色模式
  7. 漂亮的用户界面

对于这个综合项目,我真正喜欢的是,不是孤立地学习东西,而是从设计到最终部署的整个开发过程。

https://medium.com/@hamedbaat...

4. 使用 Svelte 构建一个 To-Do 应用

与React,Vue和Angular相比,Svelte 还很新,但仍是热门之一。好的,To-Do应用不一定是那里最热门的项目,但这确实可以帮助你提高Svelte技能,如下:

k4.png

你将学到什么?

本教程将向你展示如何从头到尾使用Svelte3制作应用。 它利用了组件,样式和事件处理程序。

技术栈/点

  1. Svelte 3
  2. Components
  3. CSS
  4. ES6语法

Svelte 没有太多优秀的入门项目,这个是我觉得不错的一个上手项目:https://medium.com/codingthes...

5. 使用 Next.js 构建购物车

Next.js 是一个轻量级的 React 服务端渲染应用框架,该项目将向你展示如何构建一个如下所示的购物车:

k5.jpeg

你将学到什么?

在这个项目中,你将学习如何设置 Next.js 的开发环境,创建新页面和组件,获取数据,设置样式并部署一个 next 应用。

技术栈/点

  1. Next.js
  2. 组件和页面
  3. 数据获取
  4. 样式
  5. 部署
  6. SSR和SPA

你可以在此处找到该教程:https://snipcart.com/blog/nex...

6. 使用 Nuxt.js 构建一个多语言博客网站

Nuxt.js 是 Vue 服务端渲染应用框架。你可以创建一个如下所示的应用程序:

K6.jpg

你将学到什么?

这个示例项目从初始设置到最终部署一步一步教你如何使用 Nuxt.js 构建一个完整的网站。它使用了 Nuxt 提供的许多出色功能,如页面和组件以及SCSS样式。

技术栈/点

  • Nuxt.js
  • 组件和页面
  • Storyblok模块
  • Mixins
  • Vuex
  • SCSS
  • Nuxt中间件

这个项目包含了涵盖了 Nuxt.js 的许多出色功能。我个人很喜欢使用 Nuxt 进行开发,你应该尝试使用它,这将使你成为更好的 Vue 开发人员!https://www.storyblok.com/tp/...

除此之外,我还找到了一个B站的视频:https://www.bilibili.com/vide...

7. 使用 Gatsby 构建一个博客

Gatsby是一个出色的静态站点生成器,它允许使用React作为渲染引擎引擎来搭建一个静态站点,它真正具有现代web应用程序所期望的所有优点。该项目如下:

k7.png

你将学到什么?

在本教程中,你将学习如何利用 Gatsby 构建出色的博客。

技术栈/点

  1. Gatsby
  2. React
  3. GraphQL
  4. Plugins & Themes
  5. MDX / Markdown
  6. Bootstrap CSS
  7. Templates

如果你想创建博客,这个示例教你如何利用 React 和 GraphQL 来搭建。并不是说 Wordpress 是一个不好的选择,但是有了 Gatsby ,你可以在使用 React 的同时创建高性能站点!

https://blog.bitsrc.io/how-to...

8. 使用 Gridsome 构建一个博客

Gridsome 和 Vue的关系与 Gatsby 和 React 的关系一样。Gridsome 和 Gatsby 都使用 GraphQL 作为数据层,但是 Gridsome 使用的是 VueJS。这也是一个很棒的静态站点生成器,它将帮助您创建出色的博客:

k8.png

你将学到什么?

该项目将教你如何使用 Gridsome,GraphQL 和 Markdown 构建一个简单的博客,它还介绍了如何通过Netlify 部署应用程序。

技术栈/点

  1. Gridsome
  2. Vue
  3. GraphQL
  4. Markdown
  5. Netlify

当然,这不是最全面的教程,但涵盖了 Gridsome 和 Markdown 的基本概念,可能是一个很好的起点。

https://www.telerik.com/blogs...

9.使用 Quasar 构建一个类似 SoundCloud 的音频播放器

Quasar 是另一个 Vue 框架,也可以用于构建移动应用程序。 在这个项目中,你将创建一个音频播放器应用程序,如下所示:

k9.jpeg

你将学到什么?

不少项目主要关注Web应用程序,但这个项目展示了如何通过 Quasar 框架创建移动应用程序。你应该已经配置了可工作的 Cordova 设置,并配置了 android studio / xcode。 如果没有,在教程中有一个指向quasar 网站的链接,在那里你可以学习如何进行设置。

技术栈/点

  • Quasar
  • Vue
  • Cordova
  • Wavesurfer
  • UI Components

一个展示了Quasar在构建移动应用程序方面的强大功能的小项目:https://www.learningsomething...

总结

本文展示了你可以构建的9个项目,每个项目专注于一个JavaScript框架或库。现在,你可以自行决定:使用以前未使用的框架来尝试一些新的东西或是通过做一个项目来提升已有的技能,或者在2020年完成所有项目?

定稿.png

查看原文

赞 337 收藏 257 评论 20

焦伟奇 关注了专栏 · 2020-01-02

code秘密花园

基础知识、算法、原理、项目、面试。公众号code秘密花园

关注 5211

焦伟奇 发布了文章 · 2019-10-30

css 之空格处理

1、空格规则

HTML 代码的空格通常会被浏览器忽略。

<p>  hello  world  </p>

上面是一行 HTML 代码,文字的前部、内部和后部各有两个空格。
浏览器的输出结果如下: hello world

可以看到,文字的前部和后部的空格都会忽略,内部的连续空格只会算作一个。这就是浏览器处理空格的基本规则。
如果希望空格原样输出,可以使用<pre>标签。

<pre>  hello  world  </pre>
另一种方法是,改用 HTML 实体&nbsp;表示空格。
<p>&nbsp;&nbsp;hello&nbsp;&nbsp;world&nbsp;&nbsp;</p>

2、空格字符

HTML 处理空格的规则,适用于多种字符。除了普通的空格键,还包括制表符(t)和换行符(r和n)。
浏览器会自动把这些符号转成普通的空格键。

<p>hello
world</p>

上面代码中,文本内部包含了一个换行符,浏览器视同为空格,输出结果如下: hello world

所以,文本内部的换行是无效的(除非文本放在<pre>标签内)。

<p>hello<br>world</p>

上面代码使用
标签显式表示换行

3、CSS white-space 属性

HTML 语言的空格处理,基本上就是直接过滤。这样的处理过于粗糙,完全忽视了原始文本内部的空格可能是有意义的。

CSS 提供了一个white-space属性,可以提供更精确一点的空格处理方式。该属性共有六个值,除了一个通用的inherit(继承父元素),下面依次介绍剩下的五个值。

  • 3.1 white-space: normal

    white-space属性的默认值为normal,表示浏览器以正常方式处理空格。

    html:
        <p>  hellohellohello hello
        world
        </p>
    style:
        p {
            width: 100px;
            background: red;
        }

    上面代码中,文本前部有两个空格,内部有一个长单词和一个换行符。

    文首的空格被忽略。由于容器太窄,第一个单词溢出容器,然后在后面一个空格处换行。文本内部的换行符自动转成了空格。

  • 3.2 white-space: nowrap

    white-space属性为nowrap时,不会因为超出容器宽度而发生换行。

    p {
        white-space: nowrap;
    }

    所有文本显示为一行,不会换行。

  • 3.3 white-space: pre

    white-space属性为pre时,就按照<pre>标签的方式处理。

    p {
        white-space: pre;
    }

    上面结果与原始文本完全一致,所有空格和换行符都保留了。

  • 3.4 white-space: pre-wrap

    white-space属性为pre-wrap时,基本还是按照<pre>标签的方式处理,唯一区别是超出容器宽度时,会发生换行。

    p {
        white-space: pre-wrap;
    }

    文首的空格、内部的空格和换行符都保留了,超出容器的地方发生了折行。

  • 3.5 white-space: pre-line

    white-space属性为pre-line时,意为保留换行符。除了换行符会原样输出,其他都与white-space:normal规则一致。

    p {
        white-space: pre-line;
    }

    除了文本内部的换行符没有转成空格,其他都与normal的处理规则一致。这对于诗歌类型的文本很有用。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 18 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2019-10-12
个人主页被 648 人浏览