<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Zhuotao Lian - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Zhuotao Lian - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 19:37:52 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/zhuotao/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 JavaScript 控制台改善工作流程 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How you can improve your workflow using the JavaScript console [https://www.freecodecamp.org/news/how-you-can-improve-your-workflow-using-the-javascript-console-bdd7823a9472/] ，作者：Riccardo Canella 作为一名 web 开发者，你一定清楚调试代码的必要性。我们经常使用外部库来记录日志，并在某些情况下格式化并/或显示它们。但浏览器的控制台，远比我们想象的强大。 当提到控制台时，我们会首先想到 console.log，没错吧？但它还拥有许多其他的方法。 现在，我们将了解如何充分利用控制台。我还会给出一些提示，以便你更好地理解这些方法。 什么是控制台？ JavaScript 控制台是现代浏览器中内置的开箱即用的开发工具，其界面类似 Shell。 它允许开发人员执行以下操作：  * 查看网页上发生的错误和警告的日志。  * 使用 JavaScript 命令与网页进行交互。  * 调试应用程序 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-you-can-improve-your-workflow-using-the-javascript-console/</link>
                <guid isPermaLink="false">5f082fbddb4be8080eb711cc</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zhuotao Lian ]]>
                </dc:creator>
                <pubDate>Mon, 21 Feb 2022 09:11:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/07/1_U62GMx7Z7U56CArkK2tfCQ.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/how-you-can-improve-your-workflow-using-the-javascript-console-bdd7823a9472/">How you can improve your workflow using the JavaScript console</a>，作者：Riccardo Canella</p><p>作为一名 web 开发者，你一定清楚调试代码的必要性。我们经常使用外部库来记录日志，并在某些情况下格式化并/或显示它们。但浏览器的控制台，远比我们想象的强大。</p><p>当提到控制台时，我们会首先想到 <code>console.log</code>，没错吧？但它还拥有许多其他的方法。 现在，我们将了解如何充分利用控制台。我还会给出一些提示，以便你更好地理解这些方法。</p><h3 id="-">什么是控制台？</h3><p>JavaScript 控制台是现代浏览器中内置的开箱即用的开发工具，其界面类似 Shell。 它允许开发人员执行以下操作：</p><ul><li>查看网页上发生的错误和警告的日志。</li><li>使用 JavaScript 命令与网页进行交互。</li><li>调试应用程序并直接在浏览器中遍历 DOM。</li><li>检查并分析网络活动。</li></ul><p>简言之，它使你得以在浏览器中编写，管理和监视 JavaScript。</p><h4 id="console-log-console-error-console-warn-console-info">Console.log，Console.error，Console.warn 和 Console.info</h4><p>这些可能是最常用的方法，它们允许你传递多个参数。每个参数都将被评估，并以空格分隔连接成字符串，但对于对象或数组，你可以查看其内在属性。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-14.png" class="kg-image" alt="image-14" width="600" height="400" loading="lazy"></figure><h4 id="console-group">Console.group</h4><p>该方法允许你将一系列控制台日志（也包括错误信息等）归纳在可折叠的小组下。语法非常简单：只需在 <code>console.group()</code> 后输入所需的 <code>console.log</code>（或输入 <code>console.groupColladed()</code> 来让其以默认方式关闭），然后在末尾添加 <code>console.groupEnd()</code> 来结束分组。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-15.png" class="kg-image" alt="image-15" width="600" height="400" loading="lazy"><figcaption>console.group 使用示例</figcaption></figure><p>结果如下所示：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-16.png" class="kg-image" alt="image-16" width="600" height="400" loading="lazy"></figure><h4 id="console-table">Console.table</h4><p>自从发现 <code>console.table</code>，我的生活发生了改变。在 console.log 中显示 JSON 或超大的 JSON 数组是一种令人恐惧的体验。<code>console.table</code> 使我们可以在漂亮的表格中可视化这些结构，它允许通过传递参数的方式命名列。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-17.png" class="kg-image" alt="image-17" width="600" height="400" loading="lazy"><figcaption>console.table 使用示例</figcaption></figure><p>结果非常令人满意，并且对于调试非常有用：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-18.png" class="kg-image" alt="image-18" width="600" height="400" loading="lazy"></figure><h4 id="console-count-console-time-console-timeend">Console.count，Console.time 和 Console.timeEnd</h4><p>对于每一名需要调试的开发人员，这三种方法都如同瑞士军刀。 <code>console.count</code> 能计数并输出在同一行和相同标签上调用 <code>count()</code> 的次数。<code>console.time</code> 能启动以指定输入参数为名的计时器，并且最多能在给定页面上同时运行一万个计时器。 一旦启动，我们可以通过调用 <code>console.timeEnd</code> 来停止计时器，并将经过的时间打印到控制台。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-19.png" class="kg-image" alt="image-19" width="600" height="400" loading="lazy"><figcaption>console.time 和 console.count 使用示例</figcaption></figure><p>输出如下所示：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-20.png" class="kg-image" alt="image-20" width="600" height="400" loading="lazy"></figure><h4 id="console-trace-console-assert">Console.trace 和 Console.assert</h4><p>这些方法的功能只是从其被调用的位置打印堆栈跟踪。 假设你正在创建一个 JS 库，并且想告知用户错误发生的位置。 在这种情况下，这些方法可能会非常有用。<code>console.assert</code> 类似于 <code>console.trace</code>，但仅在条件未通过时才会打印。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-21.png" class="kg-image" alt="image-21" width="600" height="400" loading="lazy"></figure><p>如你所见，输出正是产生异常时 React（或任何其他库）向我们所展示的内容。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-22.png" class="kg-image" alt="image-22" width="600" height="400" loading="lazy"></figure><h3 id="-consoles">删除所有的 Consoles?</h3><p>使用控制台通常会迫使我们消除它们。有时我们会忘记是生产版本（并且几天后才粗心地注意到这些控制台输出）。当然，我不建议任何人在不需要控制台的时候滥用它（在看到输入更改生效后就可以删除控制台命令）。你应该将错误日志或跟踪日志保留在开发模式下，以帮助你调试。不论是在工作中还是在我自己的项目中，我都经常使用 Webpack。它允许你使用 [uglifyjs-webpack-plugin] <a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin">1</a> 从生产版本中（按类型）删除所有不想保留的控制台。</p><pre><code>const UglifyJsPlugin = require('uglifyjs-webpack-plugin')var debug = process.env.NODE_ENV !== "production";.....optimization: {        minimizer: !debug ? [            new UglifyJsPlugin({                // Compression specific options                uglifyOptions: {                    // Eliminate comments                    comments: false,                    compress: {                       // remove warnings                       warnings: false,                       // Drop console statements                       drop_console: true                    },                }           })] : []}
</code></pre><p>这个配置确实很简单，且简化了工作，所以愉快地使用控制台吧（但不要滥用它！）</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React 服务端渲染解析与实践 ]]>
                </title>
                <description>
                    <![CDATA[ 让我们仔细研究下这个能让你用 React 构建通用应用程序的特性吧。 服务端渲染（以下简称 SSR）是前端框架在后端系统上运行时渲染。如果一个应用程序在服务端和客户端都可以渲染，那么它被称作通用应用程序（universal app）。 为何需要 SSR 呢? 我们应该先了解 Web 应用程序在过去 10 年的发展历程，这有助于我们理解这个问题。 这与 单页应用 [https://medium.com/@NeotericEU/single-page-application-vs-multiple-page-application-2591588efe58] （以下简称 SPA）的兴起密切相关。与传统的服务端渲染应用相比，SPA 在速度和用户体验方面具有巨大优势。 但问题随之而来。SPA 的初始服务端请求，通常返回带有一组 CSS 和 JavaScript（JS）链接的空 HTML  文件，然后需要提取外部文件以呈现相关标记。 这意味着用户将等待更长的时间才能进行初始渲染，也意味着爬虫可能会将你的页面解析为空白。 因此，其解决方法是首先在服务端渲染应用，然后在客户端使用 SPA ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/demystifying-reacts-server-side-render/</link>
                <guid isPermaLink="false">5e37e00fca1efa04e196b064</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 服务器 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zhuotao Lian ]]>
                </dc:creator>
                <pubDate>Sun, 13 Sep 2020 09:35:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1580659833244-cd7a4f35d8e0.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>让我们仔细研究下这个能让你用 React 构建<strong>通用应用程序</strong>的特性吧。</p><p><strong>服务端渲染</strong>（以下简称 SSR）是<strong>前端框架</strong>在<strong>后端系统</strong>上运行时渲染。如果一个应用程序在服务端和客户端都可以渲染，那么它被称作<strong>通用应用程序</strong>（universal app）。</p><h3 id="-ssr-">为何需要 SSR 呢?</h3><p>我们应该先了解 Web 应用程序在过去 10 年的发展历程，这有助于我们理解这个问题。</p><p>这与 <a href="https://medium.com/@NeotericEU/single-page-application-vs-multiple-page-application-2591588efe58" rel="nofollow">单页应用</a>（以下简称 SPA）的兴起密切相关。与传统的服务端渲染应用相比，SPA 在速度和用户体验方面具有巨大优势。</p><p>但问题随之而来。SPA 的初始服务端请求，通常返回带有一组 CSS 和 JavaScript（JS）链接的<strong>空</strong> <strong>HTML</strong> 文件，然后需要提取外部文件以呈现相关标记。</p><p>这意味着用户将等待更长的时间才能进行<strong>初始渲染</strong>，也意味着爬虫可能会将你的页面解析为空白。</p><p>因此，其解决方法是首先在服务端渲染应用，然后在客户端使用 SPA。</p><p><strong>SSR + SPA = 通用应用</strong></p><p>你可能会在其他文章中看到“同构应用（isomorphic app）”这个词，这跟“通用应用（universal app）”是一回事。</p><p>现在，用户不必等待 JS 加载，并能在初始请求返回响应后，立即获得<strong>完全渲染的 HTML</strong>。</p><p>试想一下，这能给使用 3G 网络的用户带来多么大的便利。你几乎可以立即在屏幕上获取内容，而不必浪费 20 多秒等它加载完成。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/02/image-14.png" class="kg-image" alt="image-14" width="600" height="400" loading="lazy"></figure><p>现在，所有发至服务器的请求都将返回完全渲染后的 HTML，这对 SEO 部门来说是个好消息！</p><p><a href="https://en.wikipedia.org/wiki/Web_crawler" rel="nofollow">爬虫</a>会像<strong>索引</strong>网络上的其他静态网站那样，<strong>索引</strong>你在服务器上呈现的所有内容。</p><p>综上所述，SSR 有两个主要优点：</p><ul><li>更快的初始页面渲染</li><li>完全可索引的 HTML 页面</li></ul><h2 id="-ssr">逐步理解 SSR</h2><p>我们采取迭代的方法来构建完整的 SSR 示例，从用于服务端渲染的 React API 开始，逐步添加内容。</p><p>你可以 follow <a href="https://github.com/alexnm/react-ssr">这个仓库</a>并查看为每个构建步骤定义的标签。</p><h3 id="-">基本设置</h3><p>首先，为了使用 SSR，我们需要一台服务器。我们将使用一个简单的 Express 应用来渲染 React 应用程序。</p><pre><code class="language-javascript">import express from "express";
import path from "path";

import React from "react";
import { renderToString } from "react-dom/server";
import Layout from "./components/Layout";

const app = express();

app.use( express.static( path.resolve( __dirname, "../dist" ) ) );

app.get( "/*", ( req, res ) =&gt; {
    const jsx = ( &lt;Layout /&gt; );
    const reactDom = renderToString( jsx );

    res.writeHead( 200, { "Content-Type": "text/html" } );
    res.end( htmlTemplate( reactDom ) );
} );

app.listen( 2048 );

function htmlTemplate( reactDom ) {
    return `
        &lt;!DOCTYPE html&gt;
        &lt;html&gt;
        &lt;head&gt;
            &lt;meta charset="utf-8"&gt;
            &lt;title&gt;React SSR&lt;/title&gt;
        &lt;/head&gt;
        
        &lt;body&gt;
            &lt;div id="app"&gt;${ reactDom }&lt;/div&gt;
            &lt;script src="./app.bundle.js"&gt;&lt;/script&gt;
        &lt;/body&gt;
        &lt;/html&gt;
    `;
}</code></pre><p>在第十行，我们指定了 Express 要服务的位于输出文件夹中的静态文件。</p><p>我们创建了一个处理所有非静态请求的路由，该路由将返回渲染完毕的 HTML。</p><p>我们使用 <code>renderToString</code> （第 13 到 14 行）来将起始 JSX 转化成要插入到 HTML 模板中的 <code>string</code> 。</p><p>请注意，我们在客户端和服务端代码中使用了相同的 Babel 插件，因此 JSX 和 ES Modules 可以在 <code>server.js</code> 中运行。</p><p>客户端上相应的函数目前是 <code>ReactDOM.hydrate</code>，该函数将使用服务端渲染的 React 应用程序，并将附加事件处理程序。</p><pre><code class="language-javascript">import ReactDOM from "react-dom";
import Layout from "./components/Layout";

const app = document.getElementById( "app" );
ReactDOM.hydrate( &lt;Layout /&gt;, app );</code></pre><p>你可以查看<a href="https://github.com/alexnm/react-ssr/tree/basic">仓库</a>中的 <code>basic</code> 标签来浏览完整示例。</p><p>搞定了，你刚刚创建了第一个<strong>服务端渲染的</strong> React 应用!</p><h4 id="react-">React 路由</h4><p>坦白讲，该应用的功能还不够丰富。所以我们添加一些路由，来看看如何处理服务端部分。</p><pre><code class="language-javascript">import { Link, Switch, Route } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Contact from "./Contact";

export default class Layout extends React.Component {
    /* ... */

    render() {
        return (
            &lt;div&gt;
                &lt;h1&gt;{ this.state.title }&lt;/h1&gt;
                &lt;div&gt;
                    &lt;Link to="/"&gt;Home&lt;/Link&gt;
                    &lt;Link to="/about"&gt;About&lt;/Link&gt;
                    &lt;Link to="/contact"&gt;Contact&lt;/Link&gt;
                &lt;/div&gt;
                &lt;Switch&gt;
                    &lt;Route path="/" exact component={ Home } /&gt;
                    &lt;Route path="/about" exact component={ About } /&gt;
                    &lt;Route path="/contact" exact component={ Contact } /&gt;
                &lt;/Switch&gt;
            &lt;/div&gt;
        );
    }
}</code></pre><p>现在 <code>Layout</code> 组件会在服务端渲染多个路由。</p><p>我们需要模拟服务器端的路由，主要的更改如下所示。</p><pre><code class="language-javascript">/* ... */
import { StaticRouter } from "react-router-dom";
/* ... */

app.get( "/*", ( req, res ) =&gt; {
    const context = { };
    const jsx = (
        &lt;StaticRouter context={ context } location={ req.url }&gt;
            &lt;Layout /&gt;
        &lt;/StaticRouter&gt;
    );
    const reactDom = renderToString( jsx );

    res.writeHead( 200, { "Content-Type": "text/html" } );
    res.end( htmlTemplate( reactDom ) );
} );

/* ... */</code></pre><p>在服务端，我们需要将 React 应用程序封装进 <code>StaticRouter</code> 组件中并为其提供 <code>location</code>。</p><p>附带说明一下，<code>context</code> 用于在渲染 React DOM 时追踪潜在的重定向。这需要通过服务端的 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#Redirection_messages" rel="nofollow">3XX 响应</a>来处理。</p><p>你可以查看<a href="https://github.com/alexnm/react-ssr/releases/tag/router">相同仓库</a>中的 <code>router</code> 标签来浏览有关路由的完整示例。</p><h4 id="redux">Redux</h4><p>现在我们已经具备了路由功能，我们来整合 <a href="https://redux.js.org/" rel="nofollow">Redux</a>。</p><p>在简单的情况下，我们需要 Redux 来处理客户端的状态管理。但如果我们需要基于状态来渲染部分部分 DOM 呢？这就需要在服务端初始化 Rudux 了。</p><p>如果你的应用在<strong>服务端</strong>上<strong>调度操作</strong> ，那就需要<strong>捕获</strong>该状态并将其和 HTML 一同发送。在客户端，我们将初始状态输入 Redux。</p><p>首先来看服务端：</p><pre><code class="language-javascript">/* ... */
import { Provider as ReduxProvider } from "react-redux";
/* ... */

app.get( "/*", ( req, res ) =&gt; {
    const context = { };
    const store = createStore( );

    store.dispatch( initializeSession( ) );

    const jsx = (
        &lt;ReduxProvider store={ store }&gt;
            &lt;StaticRouter context={ context } location={ req.url }&gt;
                &lt;Layout /&gt;
            &lt;/StaticRouter&gt;
        &lt;/ReduxProvider&gt;
    );
    const reactDom = renderToString( jsx );

    const reduxState = store.getState( );

    res.writeHead( 200, { "Content-Type": "text/html" } );
    res.end( htmlTemplate( reactDom, reduxState ) );
} );

app.listen( 2048 );

function htmlTemplate( reactDom, reduxState ) {
    return `
        /* ... */
        
        &lt;div id="app"&gt;${ reactDom }&lt;/div&gt;
        &lt;script&gt;
            window.REDUX_DATA = ${ JSON.stringify( reduxState ) }
        &lt;/script&gt;
        &lt;script src="./app.bundle.js"&gt;&lt;/script&gt;
        
        /* ... */
    `;
}</code></pre><p>它看上去一点也不美观，但我们需要将完整的 JSON 状态和 HTML 一起发送。</p><p>接下来看看客户端：</p><pre><code class="language-javascript">import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider as ReduxProvider } from "react-redux";

import Layout from "./components/Layout";
import createStore from "./store";

const store = createStore( window.REDUX_DATA );

const jsx = (
    &lt;ReduxProvider store={ store }&gt;
        &lt;Router&gt;
            &lt;Layout /&gt;
        &lt;/Router&gt;
    &lt;/ReduxProvider&gt;
);

const app = document.getElementById( "app" );
ReactDOM.hydrate( jsx, app );</code></pre><p>请注意，我们调用了两次 <code>createStore</code>，首先在服务端，然后在客户端。但是，我们使用保存在服务端的任一状态来初始化客户端。这个过程就像 DOM hydration。</p><p>你可以查看 <a href="https://github.com/alexnm/react-ssr/releases/tag/redux">相同仓库</a> 的 <code>redux</code> 标签来浏览完整示例。</p><h4 id="--1">获取数据</h4><p>最后一个难题就是数据的加载，这有点棘手。</p><p>假设我们有一个提供 JSON 数据的API。</p><p>在我们的代码库中，我<a href="https://ergast.com/mrd/" rel="nofollow">利用一个公共的 API</a> 获取了 2018 年 Formula 1 赛季的所有事件。假设我们要在<strong>主页</strong>显示所有事件。</p><p>在 React 应用挂载完成并渲染完所有内容后，我们能从客户端调用 API。但这会导致糟糕的用户体验，可能在用户看到相关内容之前，页面会显示正在加载。</p><p>我们已经有了 Redux 来在服务端存储数据以及发送数据至客户端。</p><p>如果我们在服务端调用 API，将结果存在 Redux 中，然后用相关数据来渲染完整的 HTML 给客户端，会如何呢？</p><p>但我们怎么知道应该调用哪些呢？</p><p>首先，我们需要以一种不同的方式来声明路由，所以我们看一下路由配置文件。</p><pre><code class="language-javascript">export default [
    {
        path: "/",
        component: Home,
        exact: true,
    },
    {
        path: "/about",
        component: About,
        exact: true,
    },
    {
        path: "/contact",
        component: Contact,
        exact: true,
    },
    {
        path: "/secret",
        component: Secret,
        exact: true,
    },
];</code></pre><p>然后我们静态声明每个组件的数据需求。</p><pre><code class="language-javascript">/* ... */
import { fetchData } from "../store";

class Home extends React.Component {
    /* ... */

    render( ) {
        const { circuits } = this.props;

        return (
            /* ... */
        );
    }
}
Home.serverFetch = fetchData; // static declaration of data requirements

/* ... */</code></pre><p>请注意，你可以随意命名 <code>serverFetch</code> 。</p><p>附带说明一下， <code>fetchData</code> 是一个 <a href="https://github.com/gaearon/redux-thunk">Redux thunk action</a>，它返回一个 Promise。</p><p>在服务端，我们使用一个名为 <code>matchRoute</code> 的特别的函数，它来自 <code>react-router</code>。</p><pre><code class="language-javascript">/* ... */
import { StaticRouter, matchPath } from "react-router-dom";
import routes from "./routes";

/* ... */

app.get( "/*", ( req, res ) =&gt; {
    /* ... */

    const dataRequirements =
        routes
            .filter( route =&gt; matchPath( req.url, route ) ) // filter matching paths
            .map( route =&gt; route.component ) // map to components
            .filter( comp =&gt; comp.serverFetch ) // check if components have data requirement
            .map( comp =&gt; store.dispatch( comp.serverFetch( ) ) ); // dispatch data requirement

    Promise.all( dataRequirements ).then( ( ) =&gt; {
        const jsx = (
            &lt;ReduxProvider store={ store }&gt;
                &lt;StaticRouter context={ context } location={ req.url }&gt;
                    &lt;Layout /&gt;
                &lt;/StaticRouter&gt;
            &lt;/ReduxProvider&gt;
        );
        const reactDom = renderToString( jsx );

        const reduxState = store.getState( );

        res.writeHead( 200, { "Content-Type": "text/html" } );
        res.end( htmlTemplate( reactDom, reduxState ) );
    } );
} );

/* ... */</code></pre><p>这样，我们就获得了当 React 在当前 URL 下被渲染成字符串时所需装载的组件列表。</p><p>我们收集了 <em>data requirements</em> 并等待所有调用的 API 的返回值。最终，我们恢复服务端渲染，这时 Redux 中已经得到了数据。</p><p>可以查看<a href="https://github.com/alexnm/react-ssr/tree/fetch-data">相同仓库</a>的 <code>fetch-data</code> 标签来浏览完整示例。</p><p>你可能会注意到，这导致了性能损失，因为我们在获取数据之后才进行渲染。</p><p>这时你就需要权衡了，你需要尽可能弄清楚哪些调用是必不可少的。举例来说，对于一个电商应用，获取产品列表是至关重要的，需要尽快加载，但是价格以及侧边栏选择器可以被延迟加载。</p><h4 id="helmet">Helmet</h4><p>作为 SSR 的奖励，让我们来看看 SEO。在使用 React 时，你可能想在 <code>&lt;head&gt;</code> 标签中设置不同的值，例如 title , meta tags , key words 等等。</p><p>请注意， <code>&lt;head&gt;</code> 标签通常不属于 React 应用。</p><p>在这种情况下，<a href="https://github.com/nfl/react-helmet">react-helmet</a> 提供了相应的解决方案，并对 SSR 有着很好的支持。</p><pre><code class="language-javascript">import React from "react";
import Helmet from "react-helmet";

const Contact = () =&gt; (
    &lt;div&gt;
        &lt;h2&gt;This is the contact page&lt;/h2&gt;
        &lt;Helmet&gt;
            &lt;title&gt;Contact Page&lt;/title&gt;
            &lt;meta name="description" content="This is a proof of concept for React SSR" /&gt;
        &lt;/Helmet&gt;
    &lt;/div&gt;
);

export default Contact;</code></pre><p>你只需要将 <code>head</code> 数据添加到组件树的任意位置，这使得你可以在客户端更改已挂载的 React 应用以外的值。</p><p>现在，我们添加了对 SSR 的支持：</p><pre><code class="language-javascript">/* ... */
import Helmet from "react-helmet";
/* ... */

app.get( "/*", ( req, res ) =&gt; {
    /* ... */
        const jsx = (
            &lt;ReduxProvider store={ store }&gt;
                &lt;StaticRouter context={ context } location={ req.url }&gt;
                    &lt;Layout /&gt;
                &lt;/StaticRouter&gt;
            &lt;/ReduxProvider&gt;
        );
        const reactDom = renderToString( jsx );
        const reduxState = store.getState( );
        const helmetData = Helmet.renderStatic( );

        res.writeHead( 200, { "Content-Type": "text/html" } );
        res.end( htmlTemplate( reactDom, reduxState, helmetData ) );
    } );
} );

app.listen( 2048 );

function htmlTemplate( reactDom, reduxState, helmetData ) {
    return `
        &lt;!DOCTYPE html&gt;
        &lt;html&gt;
        &lt;head&gt;
            &lt;meta charset="utf-8"&gt;
            ${ helmetData.title.toString( ) }
            ${ helmetData.meta.toString( ) }
            &lt;title&gt;React SSR&lt;/title&gt;
        &lt;/head&gt;
        
        /* ... */
    `;
}</code></pre><p>现在，我们得到了一个功能完备的 React SSR 示例！</p><p>我们从使用 Express 应用进行简单的 HTML 渲染开始，逐步增加路由、状态管理和数据获取，最终，我们处理了 React 应用以外的更改。</p><p>最终的代码库位于前面提到的<a href="https://github.com/alexnm/react-ssr">相同仓库</a>的 <code>master</code> 分支上。</p><h2 id="--2">总结</h2><p>如你所见，SSR 算不上多大的难题，但它可以变得复杂。如果你一步步构建自己的需求，那会简单很多。</p><p>在应用中添加 SSR 有必要吗？和往常一样，这需要结合实际情况。如果你的网站是公开的，并且可以供成千上万的用户访问，那答案就是必须的。但如果你要构建一个工具/仪表板之类的应用，那就没什么必要了。</p><p>无论如何，利用好通用应用，是前端社区的一大进步。</p><p>你用过类似 SSR 的方法吗？或者你是否认为这篇文章存在纰漏？欢迎留言讨论。</p><p>如果你认为这篇文章对你有帮助，请在社区中分享吧！</p><p>原文：<a href="https://www.freecodecamp.org/news/demystifying-reacts-server-side-render-de335d408fe4/">https://www.freecodecamp.org/news/demystifying-reacts-server-side-render-de335d408fe4/</a>，作者：<a href="https://www.freecodecamp.org/news/author/alexnm/">Alex Moldovan</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 正确理解 CSS 选择器的权值 ]]>
                </title>
                <description>
                    <![CDATA[ 前言 直接说结论，CSS 选择器（selector）的权值（以下直接称为“CSS 权值”）不应以进制来理解，因此进制既不是 10，100，也不是 256。 笔者看到过很多文章，发现有很大一部分人认为 CSS 权值是 256 进制甚至是 10 进制的。 下文首先解释 CSS 权值应该如何正确理解，然后尝试解释为何有人会理解为 256 进制。 本文代码运行环境：  * Google Chrome 版本 80.0.3987.149（正式版本）  * Firefox 74.0 (64 位)  * Opera 版本：67.0.3575.97  * Safari 版本13.0.2 (15608.2.30.1.1)  * 抱歉无法在 Edge 和 IE 上测试 1、何为 CSS 权值？如何正确理解与比较？ ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/an-introduction-to-css-specificity/</link>
                <guid isPermaLink="false">5e8e7703db4be8080eb7080f</guid>
                
                <dc:creator>
                    <![CDATA[ Zhuotao Lian ]]>
                </dc:creator>
                <pubDate>Wed, 15 Jul 2020 07:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/04/Music_Rock_Concert-Music_lovers_wallpaper_1366x768.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h1 id="">前言</h1>
<p>直接说结论，CSS 选择器（selector）的权值（以下直接称为“CSS 权值”）不应以进制来理解，因此进制既不是 10，100，也不是 256。<br>
笔者看到过很多文章，发现有很大一部分人认为 CSS 权值是 256 进制甚至是 10 进制的。<br>
下文首先解释 CSS 权值应该如何正确理解，然后尝试解释为何有人会理解为 256 进制。<br>
本文代码运行环境：</p>
<ul>
<li>Google Chrome 版本 80.0.3987.149（正式版本）</li>
<li>Firefox 74.0 (64 位)</li>
<li>Opera 版本：67.0.3575.97</li>
<li>Safari 版本13.0.2 (15608.2.30.1.1)</li>
<li>抱歉无法在 Edge 和 IE 上测试</li>
</ul>
<h2 id="1css">1、何为 CSS 权值？如何正确理解与比较？</h2>
<h3 id="11">1.1 定义</h3>
<blockquote>
<p><strong>Specificity</strong> is the means by which browsers decide which CSS property values are the most relevant to an element and, therefore, will be applied. Specificity is based on the matching rules which are composed of different sorts of CSS selectors. (<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">MDN web Docs</a>)</p>
</blockquote>
<blockquote>
<p>If there are two or more conflicting CSS rules that point to the same element, the browser follows some rules to determine which one is most specific and therefore wins out. Think of specificity as a score/rank that determines which style declarations are ultimately applied to an element. (<a href="https://www.w3schools.com/css/css_specificity.asp">w3schools</a>)</p>
</blockquote>
<p>在英文文档中，用 <strong>Specificity</strong> 这个单词来描述所谓的 CSS 权值。</p>
<p>Specificity 是“特异性”的意思，CSS Specificity 是应用于 CSS 选择器的规则集，以确定哪些样式应用于元素。简单地说就是特异性决定了哪些样式起作用。那么应该如何理解以及比较呢？</p>
<h3 id="12">1.2 理解及比较</h3>
<blockquote>
<p>Every selector has its place in the specificity hierarchy. There are four categories which define the specificity level of a selector.</p>
</blockquote>
<p>每个选择器在特异性层次结构中都有其位置。 有<strong>四个类别</strong>定义了选择器的特异性级别。<br>
那么我们可以理解为 <strong>A, B, C, D</strong> 四个类别，初始各个位置都是 0，即 0，0，0，0。<br>
有一张图描述得很好：</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/1_Z5vDhOz-hSY7GK1UI4RE5A-1.png" alt="1_Z5vDhOz-hSY7GK1UI4RE5A-1" width="600" height="400" loading="lazy"></p>
<h4 id="">四个类别：</h4>
<ul>
<li><strong>A:</strong> 内联样式，写在 HTML 标签中的 style=""，属于 A 类。若有，那么 A 位置加一，可以理解为 1，0，0，0。</li>
<li><strong>B:</strong> id 选择器（如 #id1），如 id=""，属于 B 类，若有，那么 B 位置加一。即 0，1，0，0。</li>
<li><strong>C:</strong> 类选择器（如 .class1）、伪类（如 :hover）、属性（如 [ type="radio" ]）为 C 类。有则 C 位置加一，即 0，0，1，0。</li>
<li><strong>D:</strong> 标签（如 div），伪元素为 D 类，若有则 D 位置加1，即 0，0，0，1。</li>
</ul>
<h4 id="">规律与注意事项</h4>
<blockquote>
<p>Universal selector (<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors"><code>*</code></a>), combinators (<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator"><code>+</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator"><code>&gt;</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator"><code>~</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator">'<code> </code>'</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Column_combinator"><code>||</code></a>) and negation pseudo-class (<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:not"><code>:not()</code></a>) have no effect on specificity. (The selectors declared <em>inside</em> <code>:not()</code> do, however.)</p>
</blockquote>
<ul>
<li>通配符选择器 （*），组合选择器 （+, &gt;, ~, '', ||） 和否定伪类 （: not ( )） 对特异性没有影响。但是在:not( )内部声明的选择器可以。</li>
</ul>
<blockquote>
<p>When an <code>important</code> rule is used on a style declaration, this declaration overrides any other declarations.</p>
</blockquote>
<ul>
<li>
<p>!important  是一种特殊的声明，它的级别高于其他所有的普通声明。<a href="https://devdocs.io/css/specificity"><strong>这个链接里</strong></a>有一些关于 !important 的使用注意事项，中文资料也很多，不再赘述。</p>
</li>
<li>
<p>比较时，A, B, C, D 四位依次比较，A 较大的权重大，不用看后面。当 A 相同时，比较 B。以此类推。</p>
</li>
<li>
<p>当 A, B, C, D 相同时，后面的规则会覆盖前面的规则。</p>
</li>
</ul>
<blockquote>
<p><strong>A class selector beats any number of element selectors</strong></p>
</blockquote>
<ul>
<li>一个类选择器可以击败任意数目的元素选择器。这就说明进制的概念在理论上是不应存在的。即 0, 0, 1, 0 应大于 0, 0, 0, D。 D 可以取任意值。因此比较权重时，还是应该按位去比。</li>
</ul>
<p><a href="http://www.standardista.com/wp-content/uploads/2012/01/specifishity1.pdf">这个链接里</a>也有一张有意思的图，方便大家理解。</p>
<h3 id="13">1.3 举例说明</h3>
<pre><code class="language-javascript">  body div {/*此处权重 = 0，0，0，2*/
      background-color: red;
  }
  div {/*此处权重 = 0，0，0，1*/
      height: 100px;
      width: 100px;
      background-color: green;
  }
  &lt;div&gt;Test CSS Specificity&lt;/div&gt;
</code></pre>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/--2020-04-08--11.16.43.png" alt="--2020-04-08--11.16.43" width="600" height="400" loading="lazy"></p>
<pre><code class="language-javascript">    div {
		width: 100px;
		height: 100px;
    }
    .testClass.testClass {/*权重为0，0，2，0*/
        background-color: yellow;
    }
    .testClass {/*权重为0，0，1，0*/
        background-color: black;
    }

    &lt;div class="testClass"&gt;Test CSS Specificity&lt;/div&gt;
</code></pre>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/--2020-04-08--11.24.55.png" alt="--2020-04-08--11.24.55" width="600" height="400" loading="lazy"></p>
<p>这里显示为黄色，说明重复的类选择器权重也会累计。</p>
<pre><code class="language-javascript">    div{
		width: 100px;
		height: 100px;
    }
    .testClass.testClass {/*权重为0，0，2，0*/
 		background-color: yellow;
    }
    .testClass {/*权重为0，0，1，0*/
        background-color: black;
    }
    #testClass {/*权重为0，1，0，0*/
        background-color: blue;
    }  	
    &lt;div class="testClass" id="testClass"&gt;Test CSS Specificity&lt;/div&gt;
</code></pre>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/--2020-04-08--11.33.20.png" alt="--2020-04-08--11.33.20" width="600" height="400" loading="lazy"></p>
<p>各种各样的例子网上很多，不再赘述。下面第二部分举一些反例证明进制的思想是不合适的。</p>
<h2 id="210100256">2、为何进制不是10，100或256呢？</h2>
<h3 id="21">2.1 反例</h3>
<p>十一个类选择器，与一个 id 选择器。显而易见的，进制并不是 10。最后还是 id 选择器奏效。</p>
<pre><code class="language-javascript">div{
    width: 100px;
	height: 100px;
}
#testId {/*权重为0，1，0，0*/
    background-color: purple;
}  	.testClass1.testClass2.testClass3.testClass4.testClass5.testClass6.testClass7.testClass8.testClass9.testClass10.testClass11 {/*权重为0，0，11，0，并没有任何作用*/
    background-color: red;
}

&lt;div class="testClass1 testClass2 testClass3 testClass4 testClass5 testClass6 testClass7 testClass8 testClass9 testClass10 testClass11" id="testId"&gt;Test CSS Specificity&lt;/div&gt;
</code></pre>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/--2020-04-08--11.47.30.png" alt="--2020-04-08--11.47.30" width="600" height="400" loading="lazy"></p>
<p>256个类选择器能干掉一个 id 选择器吗？</p>
<pre><code class="language-javascript">#id {/*权重为0，1，0，0*/
     background: red;
}
   
   .c000.c001.c002.c003.c004.c005.c006.c007.c008.c009.c010.c011.c012.c013.c014.c015.c016.c017.c018.c019.c020.c021.c022.c023.c024.c025.c026.c027.c028.c029.c030.c031.c032.c033.c034.c035.c036.c037.c038.c039.c040.c041.c042.c043.c044.c045.c046.c047.c048.c049.c050.c051.c052.c053.c054.c055.c056.c057.c058.c059.c060.c061.c062.c063.c064.c065.c066.c067.c068.c069.c070.c071.c072.c073.c074.c075.c076.c077.c078.c079.c080.c081.c082.c083.c084.c085.c086.c087.c088.c089.c090.c091.c092.c093.c094.c095.c096.c097.c098.c099.c100.c101.c102.c103.c104.c105.c106.c107.c108.c109.c110.c111.c112.c113.c114.c115.c116.c117.c118.c119.c120.c121.c122.c123.c124.c125.c126.c127.c128.c129.c130.c131.c132.c133.c134.c135.c136.c137.c138.c139.c140.c141.c142.c143.c144.c145.c146.c147.c148.c149.c150.c151.c152.c153.c154.c155.c156.c157.c158.c159.c160.c161.c162.c163.c164.c165.c166.c167.c168.c169.c170.c171.c172.c173.c174.c175.c176.c177.c178.c179.c180.c181.c182.c183.c184.c185.c186.c187.c188.c189.c190.c191.c192.c193.c194.c195.c196.c197.c198.c199.c200.c201.c202.c203.c204.c205.c206.c207.c208.c209.c210.c211.c212.c213.c214.c215.c216.c217.c218.c219.c220.c221.c222.c223.c224.c225.c226.c227.c228.c229.c230.c231.c232.c233.c234.c235.c236.c237.c238.c239.c240.c241.c242.c243.c244.c245.c246.c247.c248.c249.c250.c251.c252.c253.c254.c255 {/*权重为0，0，256，0，并没有任何作用*/
     background: blue;
}
   
test {
     display: block;
     height: 100px;
     width: 100px
}
&lt;test id="id" class="c000 c001 c002 c003 c004 c005 c006 c007 c008 c009 c010 c011 c012 c013 c014 c015 c016 c017 c018 c019 c020 c021 c022 c023 c024 c025 c026 c027 c028 c029 c030 c031 c032 c033 c034 c035 c036 c037 c038 c039 c040 c041 c042 c043 c044 c045 c046 c047 c048 c049 c050 c051 c052 c053 c054 c055 c056 c057 c058 c059 c060 c061 c062 c063 c064 c065 c066 c067 c068 c069 c070 c071 c072 c073 c074 c075 c076 c077 c078 c079 c080 c081 c082 c083 c084 c085 c086 c087 c088 c089 c090 c091 c092 c093 c094 c095 c096 c097 c098 c099 c100 c101 c102 c103 c104 c105 c106 c107 c108 c109 c110 c111 c112 c113 c114 c115 c116 c117 c118 c119 c120 c121 c122 c123 c124 c125 c126 c127 c128 c129 c130 c131 c132 c133 c134 c135 c136 c137 c138 c139 c140 c141 c142 c143 c144 c145 c146 c147 c148 c149 c150 c151 c152 c153 c154 c155 c156 c157 c158 c159 c160 c161 c162 c163 c164 c165 c166 c167 c168 c169 c170 c171 c172 c173 c174 c175 c176 c177 c178 c179 c180 c181 c182 c183 c184 c185 c186 c187 c188 c189 c190 c191 c192 c193 c194 c195 c196 c197 c198 c199 c200 c201 c202 c203 c204 c205 c206 c207 c208 c209 c210 c211 c212 c213 c214 c215 c216 c217 c218 c219 c220 c221 c222 c223 c224 c225 c226 c227 c228 c229 c230 c231 c232 c233 c234 c235 c236 c237 c238 c239 c240 c241 c242 c243 c244 c245 c246 c247 c248 c249 c250 c251 c252 c253 c254 c255"&gt;test 256&lt;/test&gt;
</code></pre>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2020/04/--2020-04-08--11.59.38.png" alt="--2020-04-08--11.59.38" width="600" height="400" loading="lazy"></p>
<p>这个 256 的测试代码修改自张鑫旭的<a href="https://www.zhangxinxu.com/wordpress/2012/08/256-class-selector-beat-id-selector/">一篇博客</a>。<br>
结果显而易见，256 个 class 并不能“击败”一个 id，所以进制不是 256。<br>
100 就不再试了。所以进制的说法，从实际看来，也是不合适的。</p>
<h4 id="but20120820256id">BUT！你可以发现，在张鑫旭的<a href="https://www.zhangxinxu.com/wordpress/2012/08/256-class-selector-beat-id-selector/">这篇博客</a>中（2012 年 08 月 20 日），256 个类选择器确实干掉了一个 id 选择器！那也就是说，进制的说法至少在当时是有据可依的。那具体是为什么呢？结合我搜索到的资料，简单的“猜想”一下。</h4>
<h3 id="22">2.2 为什么会有进制的说法？</h3>
<p>通过张鑫旭博客，我们可以看到，在当时，确实 256 个类选择器干掉了一个 id 选择器。</p>
<p>在<a href="https://www.zhangxinxu.com/wordpress/2012/08/256-class-selector-beat-id-selector/">这篇博客</a>中，他提到了：</p>
<blockquote>
<p>据说，查看FireFox浏览器的<a href="http://hg.mozilla.org/mozilla-central/file/tip/layout/style/StyleRule.cpp#l479">源代码</a>，发现，所有的类名(classes)都是以<strong>8比特数据类型</strong>存储的。对比特稍稍了解的人都知道，8比特所能hold的最大值就是255. 所以你想啊，当同时出现256个class, 势必会越过其边缘，溢出到id区域。</p>
<blockquote>
<p>Gecko overflows the count of classes into the count of IDs, each of which holds 8 bits.</p>
<p>— Cameron McCormack (@heycam) August 16, 2012</p>
</blockquote>
</blockquote>
<blockquote>
<p>根据一个Opera员工的信息，Opera浏览器class类名是以<strong>16比特数据类型</strong>存储的。因此，该浏览器要想发生class溢出到id的话，需要连续65536个class. 也不知道是不是因为16比特字符串比8自己的更影响选择器引擎</p>
<blockquote>
<p>yes, opera uses 16 bits instead of 8. bring on 65536 classes…</p>
<p>— patrick h. lauke (@patrick_h_lauke) August 16, 2012</p>
</blockquote>
</blockquote>
<p>也就是说，虽然标准是那么规定的，但是落实到浏览器上，实际效果确是不尽相同。<br>
因此我个人认为，这种不存在进制的，按级别按位比较的方式，应该更合理一些。</p>
<hr>
<p><strong>写在最后：</strong><br>
以上是我个人的理解，欢迎批评指正。<br>
大家如对 CSS 的其他话题感兴趣，也欢迎通过<a href="mailto:lianzhuotao@sina.com">我的邮箱</a>与我交流学习。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 用 7 行 JSON 代码将你的网页变成移动应用 ]]>
                </title>
                <description>
                    <![CDATA[ 一种将 web 引擎与应用程序融合的新方法 如果我告诉你题图中的 7 行橙色的 JSON 代码 就能将网站变成移动应用，你怎么看？不需要使用框架 API 重写你的网站就能让它像移动应用一样呈现。就让你现有的网站保持原样，利用简单的 URL 引用将其打包成原生应用即可。 倘若如上，你只需略微调整 JSON 代码，就可以访问所有的原生 API，原生 UI 组件以及实现原生视图转换。 下面是一个最简单的范例： 你可以发现我嵌入了一个 github.com 网页 [https://link.zhihu.com/?target=https%3A//github.com/Jasonette]，但其余布局都是本地 UI 组件，就像导航栏 [https://link.zhihu.com/?target=https%3A//docs.jasonette.com/document/%23bodyheader] 和底部标签栏 [https://link.zhihu.com/?target=https%3A//docs.jasonette.com/document/%23tabs] 。并且原生切换效果是 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-turn-your-website-into-a-mobile-app-with-7-lines-of-json/</link>
                <guid isPermaLink="false">5d9b1b58fbfdee429dc5ff6f</guid>
                
                <dc:creator>
                    <![CDATA[ Zhuotao Lian ]]>
                </dc:creator>
                <pubDate>Mon, 07 Oct 2019 11:07:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/10/1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h3 id="-web-">一种将 web 引擎与应用程序融合的新方法</h3><p>如果我告诉你<strong>题图中的 7 行橙色的 JSON 代码</strong> 就能将网站变成移动应用，你怎么看？不需要使用框架 API 重写你的网站就能让它像移动应用一样呈现。就让你现有的网站保持原样，利用简单的 URL 引用将其打包成原生应用即可。</p><p>倘若如上，你只需略微调整 JSON 代码，就可以访问所有的原生 API，原生 UI 组件以及实现原生视图转换。</p><p>下面是一个最简单的范例：</p><p>你可以发现我嵌入了一个<a href="https://link.zhihu.com/?target=https%3A//github.com/Jasonette" rel="nofollow noreferrer"> github.com 网页</a>，但其余布局都是本地 UI 组件，就像<a href="https://link.zhihu.com/?target=https%3A//docs.jasonette.com/document/%23bodyheader" rel="nofollow noreferrer">导航栏</a>和<a href="https://link.zhihu.com/?target=https%3A//docs.jasonette.com/document/%23tabs" rel="nofollow noreferrer">底部标签栏</a>。并且原生切换效果是自动生成的，你无需使用任何 API 重写网站。</p><p>你可能迫切地想问：”这很酷，但除了在原生应用框架中显示网页，你还能做其他有意义的事吗？“</p><p>问得好！因为这就是本篇文章的主题。你只需要创建一个 <strong>在 Web 视图和应用程序之间的双向通信信道</strong> ，这样父应用就可以触发 Web 视图内的任何 JavaScript 函数，并且 Web 视图可以从外部调用原生 API。</p><p>这有个简单的例子：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/10/3.gif" class="kg-image" alt="3" width="428" height="762" loading="lazy"></figure><p>请注意，此视图包含：</p><p>1.原生导航栏，包括内置的切换功能。</p><p>2.一个 Web 视图，其中嵌入了 Web 应用程序：二维码生成器。</p><p>3.底部的原生聊天输入组件。</p><p>这些都可以通过调整上面的 JSON 代码来实现。</p><p>最后，请注意，二维码会随着你输入内容的不同而变化。此输入会触发二维码 Web 应用内的 JavaScript 函数，从而重新生成二维码。</p><p>目前还没有试图从根本上解决 <strong>“将 Web 视图无缝集成到原生应用程序”</strong> 这一问题的应用开发框架，因为它们都专注于完全的原生或 HTML5。</p><p>每当你听到有人谈论移动应用的未来，你都可能会听到他们谈论 <strong>“究竟是 HTML5 还是原生方法会胜出呢？”</strong></p><p>人们觉得 <code>原生</code> 和 <code>HTML</code> 是不能共存的，更不会认为它们能相辅相成，甚至实现看似不可能的功能。</p><p>在本文中，我将阐述：</p><ul><li>为什么将 Web 引擎和原生组件相融合通常是一个好主意。</li><li>为什么 HTML 和原生的无缝融合很困难，以及如何实现。</li><li>更重要的是，你如何利用它来快速构建你自己的应用程序。</li></ul><h3 id="-html-">为什么要在原生应用中使用 HTML ？</h3><p>在进一步探讨之前，让我们首先讨论一下这个主意是好是坏，以及在何时你会想采用这种方法。这儿给出一些潜在的使用案例：</p><h3 id="1-web-">1. 使用 Web 原生功能</h3><p>你的应用的某些部分可能更适合用 Web 引擎实现。 例如，<a href="https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/WebSocket" rel="nofollow noreferrer">Websocket</a> 是一个为 Web 环境设计的原生 Web 功能。在这种情况下，安装本质上 <strong>“模拟”</strong> Websocket 的第三方库，就不如使用内置的 Web 引擎（ <strong>iOS 版的 WKWebView</strong> 和 <strong>Android 版的 WebView</strong> ）。</p><p>无需安装额外的代码，你就可以免费实现。这也引出了下一点。</p><h3 id="2-">2. 避免较大的二进制文件</h3><p>你可能想快速使用一些功能，但它们需要一个庞大的第三方库。</p><p>比如，为了以原生方式实现一个二维码生成器，你可能需要安装某些第三方库，而它们将大大增加文件的体积。但如果你使用 Web 视图引擎以及通过一个简单的 <code>&lt;script src&gt;</code> 来调用 javascript 库，你就能免费获得这些功能，且不需要安装任何第三方原生库。</p><h3 id="3-">3. 缺乏可靠的移动库</h3><p>就一些前沿技术而言，还没有可靠稳定的移动端实现。</p><p>幸运的是，它们中的大多数都有 Web 端实现，所以集成它们的最有效的方式就是使用 JavaScript 库。</p><h3 id="4-web-">4. 构建部分原生，部分基于 Web 的应用</h3><p>许多开发新手希望将他们的网站移植为移动应用，但当他们发现网站的部分特性太过复杂而无法快速为每种移动平台重写时，往往会气馁或受挫。</p><p>例如，你可能有一个网页太过复杂而无法立即转换为移动应用程序，但网站的其他部分可能很容易转换。</p><p>在这种情况下，如果可以以原生方式构建大部分应用程序，并以 HTML 形式将特别复杂的页面无缝集成于应用中，那就太棒了。</p><h3 id="-">它是如何实现的?</h3><h3 id="a-jasonette">A. Jasonette</h3><p>Jasonette 是一种开源的，基于标记的用于构建跨平台原生应用的方法。</p><p>它就像一个 Web 浏览器，但它不是将 HTML 标记语言解释为网页，而是将 JSON 标记语言解释为 IOS 或 Android 端的原生应用。</p><p>就像所有的 Web 浏览器都有完全相同的代码，却可以通过按需解释各种 HTML 标记来为你提供各种不同的 Web 应用程序一样，所有 Jasonette 应用程序都有完全相同的二进制文件，它按需解释各种 JSON 标记来创建你的应用。开发人员不需要接触代码。相反，你可以通过编写标记来构建应用程序，该标记可以实时转换为原生应用程序。</p><p>你可以在 <a href="https://link.zhihu.com/?target=https%3A//medium.freecodecamp.org/how-to-build-cross-platform-mobile-apps-using-nothing-more-than-a-json-markup-f493abec1873" rel="nofollow noreferrer">这里</a>进一步学习 Jasonette。</p><p>虽然 Jasonette 的核心是构建原生应用程序，但本文旨在介绍如何将 HTML 集成到核心原生引擎中，接下来就让我们进一步探讨。</p><h3 id="b-jasonette-web-">B. Jasonette Web 容器</h3><p>原生应用很棒，但有时我们需要使用 Web 功能。</p><p>将 Web 视图集成到原生应用中是一项复杂的工作。无缝集成需要：</p><ol><li><strong>Web 视图应该被集成为原生应用布局的一部分：</strong> Web 视图应该像其他原生 UI 组件一样，作为原生布局的一部分融入应用，否则会让用户感到笨拙，就像在访问一个真的网站一样。</li><li><strong>父应用可以控制子 Web 容器：</strong> 父应用应当可以轻松地控制子 Web 视图。</li><li><strong>子 Web 容器可以触发父应用的原生事件：</strong> 子应用应当可以触发父应用事件从而运行原生 API。</li></ol><p>这有许多工作要做，所以我首先介绍第一部分： —— <strong>简单地将 Web 容器嵌入原生布局中</strong> ——并将其作为版本1发布：</p><p><strong><a href="https://link.zhihu.com/?target=http%3A//jasonette.com/webcontainer/" rel="nofollow noreferrer">JSON Web 容器</a></strong><br><a href="https://link.zhihu.com/?target=http%3A//jasonette.com/webcontainer/" rel="nofollow noreferrer">_JSON 中的 HTML 变成了原生应用中的组件_jasonette.com</a></p><p>这已经非常实用了，但由于其不可交互性，仍然存在一些限制。</p><p>父应用无法控制子 Web 容器，子应用无法将任何事件通知父应用，这使得 Web 容器完全独立于外部世界。</p><h3 id="c-jasonette-web-2-0-">C. Jasonette Web 容器 2.0：使其可交互</h3><p>在发布了版本1之后，我进行了第二部分的实验。—— <strong>使 Web 容器变得可交互</strong></p><p>下文将解释添加的解决方案，这些解决方案使得过去的静态 Web 容器变得可交互，并显著增强了它们的功能。</p><h3 id="-web--1">实现：交互式 Web 容器</h3><h3 id="1-url-"><strong>1. 通过 URL 加载</strong></h3><h3 id="--1">问题</h3><p>先前在版本1中，想要将 Web 容器用作后台视图组件，你必须要首先 href="<a href="https://link.zhihu.com/?target=https%3A//jasonette.com/web" rel="nofollow noreferrer">https://jasonette.com/web</a><code>container/"&gt;将 $j</code>ason.<code>body.background.type 设置为 &amp;q</code>uot;html" 然后在 $jason.body.background.text 属性下硬编码 HTML 文本 ，如下所示：</p><pre><code class="language-text">{  "$jason": {    "head": {      ...    },    "body": {      "background": {        "type": "html",        "text": "&lt;html&gt;&lt;body&gt;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;"      }    }  }}</code></pre><p>当然，人们希望能使用一个简单的 Web URL 来实例化容器，而不是在一行中硬编码整个 HTML 文本。</p><h3 id="--2">解决方法</h3><p>Web 容器2.0已经添加了 <code>url</code> 属性。 你可以像这样嵌入本地 <code>file://</code> HTML（它加载自应用程序附带的本地 HTML 文件）：</p><pre><code class="language-text">{  "$jason": {    "head": {      ...    },    "body": {      "background": {        "type": "html",        "url": "file://index.html"      }    }  }}</code></pre><p>或像这样嵌入一个远程的 <code>http[s]://</code> URL（它加载自远程 HTML 文件）：</p><pre><code class="language-text">{  "$jason": {    "head": {      ...    },    "body": {      "background": {        "type": "html",        "url": "https://news.ycombinator.com"      }    }  }}</code></pre><h3 id="2-web-"><strong>2. 父应用 &lt;=&gt; Web 容器双向通信</strong></h3><h3 id="--3">问题</h3><p>此前，Web容器只用于显示内容，而非可交互的。这意味着 <strong>以下任一项都不可能实现：</strong></p><ol><li><strong>Jasonette =&gt; Web 容器</strong> : 从 Jasonette 调用 Web 容器内的 JavaScript 函数。</li><li><strong>Web 容器 =&gt; Jasonette</strong> : 从 Web 容器代码调用原生 API。</li></ol><p>你所能做的就是显示 Web 容器的内容。这就像你将 iframe 框架嵌入了网页中，但主网页无法访问 iframe 框架中的内容。</p><h3 id="--4">解决方法</h3><p>Jasonette 的重点是设计一种标准的标记语言来描述跨平台的移动应用程序。因此，我们需要它能够全面描述父应用与子 Web 容器间的通信。</p><p>为了实现这一点，我在父应用和子 Web 容器之间设计了一个基于 <code>[JSON-RPC][9]</code> 的通信信道。由于 Jasonette 上的所有内容都是用 JSON 对象表示的，所以使用 JSON-RPC 标准格式作为通信协议是非常有意义的。<br></p><figure class="kg-card kg-image-card"><img src="https://pic4.zhimg.com/80/v2-7bb5a2bdc2e07b9b18a0baf925840783_hd.jpg" class="kg-image" alt="v2-7bb5a2bdc2e07b9b18a0baf925840783_hd" width="720" height="552" loading="lazy"></figure><p>为了对 Web 容器能进行 JavaScript 函数调用我们声明了一个名为 <code>$agent.request</code> 的操作：</p><pre><code class="language-text">{  "type": "$agent.request",  "options": {    "id": "$webcontainer",    "method": "login",    "params": ["username", "password"]  }}</code></pre><p><code>[$agent.request][10]</code> 是触发 JSON-RPC 请求并发送到 Web 容器中的原生 API。要使用它，我们必须传递一个 <code>options</code> 对象作为其参数。</p><p><code>options</code> 对象是将被发送到 Web 容器的实际<a href="https://link.zhihu.com/?target=http%3A//www.jsonrpc.org/specification%23conventions" rel="nofollow noreferrer"> JSON-RPC 请求</a> 。让我们看看各个属性的含义：</p><ul><li><code>id</code> : Web 容器构建在一个名为 <a href="https://link.zhihu.com/?target=https%3A//jasonette.com/agent/" rel="nofollow noreferrer">Agent</a> 的底层架构之上。通常，一个视图可以有多个 Agent，每个 Agent 都有其唯一的ID。但是 <a href="https://link.zhihu.com/?target=https%3A//docs.jas%253Ccode%253Eonette.com/we%253C/code%253Eb/%231-background-web-container-is-an-agent" rel="nofollow noreferrer">Web 容器是一种特殊的 Agent，他只能使用 $webcontainer 作为 ID</a>, 因此我们在这里使用这个 ID。</li><li><code>method</code> : 要调用的 JavaScript 函数名</li><li><code>params</code> : 传递给 JavaScript 函数的参数数组。</li></ul><p>完整的标记如下所示：</p><pre><code class="language-text">{  "$jason": {    "head": {      "actions": {        "$load": {          "type": "$agent.request",          "options": {            "id": "$webcontainer",            "method": "login",            "params": ["alice", "1234"]          }        }      }    },    "body": {      "header": {        "title": "Web Container 2.0"      },      "background": {        "type": "html",        "url": "file://index.html"      }    }  }}</code></pre><p>此标记表示：</p><p>当视图加载 ( <code>[$jason.head.actions.$load][14]</code> )时，向 Web 容器 Agent( <code>[$agent.request][15]</code> ) 发送 JSON-RPC 请求，该请求在 <code>options</code> 下被指定。</p><p>Web 容器在 <code>[$jason.body.background][16]</code> 下被定义，在本例中将加载一个名为 <code>file://index.html</code> 的本地文件。</p><p>它将会查找一个名为 <code>login</code> 的 JavaScript 函数，并传递 <code>params</code> 下的两个参数（ <code>"alice"</code> 和 <code>"1234"</code> ）</p><pre><code class="language-text">login("alice", "1234")</code></pre><p>我只解释了父应用如何触发子 Web 容器的 JavaScript 函数调用，你也可以反其道而行之，<a href="https://link.zhihu.com/?target=https%3A//docs.jasonette.com/agents/%233-agenttrigger" rel="nofollow noreferrer">让 Web 容器触发父应用的原生 API</a>。</p><p>详情请参阅 <a href="https://link.zhihu.com/?target=https%3A//docs.jasonette.com/agents/" rel="nofollow noreferrer">Agent 文档</a>。</p><h3 id="--5">范例</h3><p>让我们回到前面简单分享的二维码示例：<br></p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/10/5.gif" class="kg-image" alt="5" width="428" height="762" loading="lazy"></figure><ol><li>其中 <a href="https://link.zhihu.com/?target=https%3A//docs.jasonette.com/document/%23input" rel="nofollow noreferrer">底部的输入组件是100%原生的</a>。</li><li>二维码是由 <a href="https://link.zhihu.com/?target=https%3A//github.com/Jasonette/Jasonpedia/blob/gh-pages/webcontainer/agent/fn/agent.html" rel="nofollow noreferrer">作为 Web 应用</a>的 Web 容器产生的。</li><li>当用户输入某些内容，并按 “Generate” 时，它将调用 Web 容器 Agent 的 <code>$agent.request</code> 操作，并进一步调用 <a href="https://link.zhihu.com/?target=https%3A//github.com/Jasonette/Jasonpedia/blob/gh-pages/webcontainer/agent/fn/agent.html%23L22" rel="nofollow noreferrer">JavaScript 函数 “qr”</a>。</li></ol><p>你可以在<a href="https://link.zhihu.com/?target=https%3A//github.com/Jasonette/Jasonpedia/blob/gh-pages/webcontainer/agent/fn/index.json" rel="nofollow noreferrer">这里</a>参阅示例。</p><h3 id="3--1"><strong>3. 脚本注入</strong></h3><h3 id="--6">问题</h3><p>有时候，你可能想在 Web 容器加载完初始 HTML 后，将 JavaScript 代码动态地注入到其中。</p><p>假设你要构建一个自定义的 Web 浏览器应用程序。你可能想将自己的自定义 JavaScript 注入到每个 Web 视图中以自定义 Web 视图的行为，这有点像 Web 浏览器的扩展。</p><p>就算你不想构建 Web 浏览器，在想对那些内容无法控制的 URL 实现自定义行为时，你也需要使用脚本注入方法。在原生应用程序和 Web 容器之间实现通信的唯一方法就是通过 <code>$agent</code> API。但若你无法更改 HTML 内容，则只能通过动态注入的方式将 <code>$agent</code> 接口添加到 Web 容器中。</p><h3 id="--7">解决方法</h3><p>就像上文提到的， <code>$jason.body.background</code> Web 容器也是一个 <code>agent</code> 。这意味着你可以使用与普通 Agent 相同的 <code>[$agent.inject][23]</code> 方法。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/10/6.png" class="kg-image" alt="6" width="800" height="676" loading="lazy"></figure><h3 id="4-url-"><strong>4. URL 点击处理</strong></h3><p>在过去，Web 容器处理链接点击只有两种方法：</p><ol><li><strong>只读：</strong> 将 Web 容器视为只读，并忽略触摸或滚动等所有事件。这样所有 Web 容器都是只读的，除非你让它表现得像常规浏览器一样，正如下文所述。</li><li><strong>常规浏览器行为：</strong> 像普通浏览器一样，让用户与页面交互。这需要将 <code>“type”：“$default”</code> 设置为 <code>“action”</code> 属性来进行声明。</li></ol><h3 id="--8">问题</h3><p>二者都是 <strong>“孤注一掷” 的方案</strong> 。</p><ul><li>在 “只读”情况下，Web 容器将忽略你所有的交互。</li><li>在 “常规浏览器行为” 的情况下，Web 容器表现得像浏览器一样。当你点击一个链接，它会像网页一样将链接内容通过刷新页面展示给你。 你无法劫持该链接并调用原生 API。</li></ul><h3 id="--9">解决方法</h3><p>使用新的 Web 容器，你可以将任何 <code>action</code> 附加到 <code>$jason.body.background</code> Web 容器以处理点击事件。<br></p><figure class="kg-card kg-image-card"><img src="https://pic3.zhimg.com/80/v2-59f1cdfb67fad1fe448af9950983c242_hd.jpg" class="kg-image" alt="v2-59f1cdfb67fad1fe448af9950983c242_hd" width="720" height="573" loading="lazy"></figure><p><br>让我们看看这个例子：</p><pre><code class="language-text">{  "$jason": {    "head": {      "actions": {        "displayBanner": {          "type": "$util.banner",          "options": {            "title": "Clicked",            "description": "Link {{$jason.url}} clicked!"          }        }      }    },    "body": {      "background": {        "type": "html",        "url": "file://index.html",        "action": {          "trigger": "displayBanner"        }      }    }  }}</code></pre><p>在这里我们将 <code>"trigger": "displayBanner"</code> 附加到了 Web 容器。这意味着当用户点击 Web 容器中的任何链接时，会触发 <code>displayBanner</code> 操作，而不是让 Web 视图处理它。</p><p>此外，若你查看 <code>displayBanner</code> 操作，你会发现 <code>$jason</code> 变量。在本例中，点击的链接将通过 <code>$jason</code> 变量传递。例如，如果你点击一个名为 <code>"https://google.com"</code> 的 URL， <code>$jason</code> 将获得下列值：</p><pre><code class="language-text">{  "url": "https://google.com"}</code></pre><p>这意味着你可以通过href="<a href="https://link.zhihu.com/?target=https%3A//docs.jasonette.com/web/%23b-intercept-url-visits" rel="nofollow noreferrer">https://docs.jasonette.com/web/#b-intercept-url-visits</a>"&gt;检查 $jason.url 值来有选择地触发不同的操作。</p><p>让我们再举一个实现自定义 Web 浏览器的例子：</p><pre><code class="language-text">{  "$jason": {    "head": {      "actions": {        "handleLink": [{          "{{#if $jason.url.indexOf('signin') !== -1 }}": {            "type": "$href",            "options": {              "url": "file://key.html"            }          }        }, {          "{{#else}}": {            "type": "$default"          }        }]      }    },    "body": {      "background": {        "type": "html",        "url": "file://index.html",        "action": {          "trigger": "handleLink"        }      }    }  }}</code></pre><p>我们测试URL是否包括 <code>signin</code> 字符串，然后根据结果执行不同的操作。</p><ol><li>若包含 <code>signin</code> ，则会打开一个新视图并以原生方式处理本地登录。</li><li>若不包含 <code>signin</code> ，则只会运行 <code>"type": "$default"</code> 操作，就像普通浏览器一样。</li></ol><h3 id="--10">使用示范</h3><h3 id="-web--2">构建自定义 Web 浏览器</h3><p>我们现今可以利用新版 Web 容器的特性来：</p><ol><li>通过 <code>url</code> 属性实现自我加载，当做一个成熟完备的浏览器</li><li>根据 URL 有选择地处理链接点击操作</li></ol><p>我们甚至可以用十几行 JSON 代码来构建一个自定义的 Web 浏览器应用。既然我们现在可以劫持每个链接的点击，那么可以看一下 <code>$jason.url</code> 并根据 URL 运行任何我们想要的操作。</p><p>让我们看看下面的例子：</p><figure class="kg-card kg-image-card"><img src="https://pic4.zhimg.com/v2-958401e4fa577065d965fa26e747a80b_b.jpg" class="kg-image" alt="v2-958401e4fa577065d965fa26e747a80b_b" width="428" height="762" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://pic3.zhimg.com/v2-c6c0eefdbaf14bb6871a46bec8ac2e26_b.jpg" class="kg-image" alt="v2-c6c0eefdbaf14bb6871a46bec8ac2e26_b" width="428" height="762" loading="lazy"></figure><p><br>在左侧，我们看到点击链接的行为类似常规浏览器 （ <code>"type": "$default"</code> ）。</p><p>在右侧，我们看到点击链接会以原生方式转换到另一个 JASON 视图。</p><p>这些都可以通过基于 <code>$jason.url</code> 选择性地触发不同的动作来实现。</p><p><strong>步骤 1.将名为</strong> <strong><code>visit</code></strong> <strong>的操作附加到 Web 容器中，如下所示：</strong></p><pre><code class="language-text">{  ...  "body": {    "background": {      "type": "html",      "url": "https://news.ycombinator.com",      "action": {        "trigger": "visit"      }    }  }}</code></pre><p><strong>步骤 2.根据</strong> <strong><code>$jason.url</code></strong> <strong>的值运行</strong> <strong><code>visit</code></strong> <strong>中的相关操作</strong></p><p>在以下代码中，我们将检查 <code>$jason.url</code> 是否与 <code>newest</code> , <code>show</code> , <code>ask</code> 等相匹配（它们是顶部菜单选项的链接）。若匹配，我们可以通过设置 <code>"type": "$default"</code> 来让 Web 容器像常规浏览器一样工作。</p><p>若模式不匹配，我们将通过原生的 <code>$href</code> 过渡到新视图，并将点击的链接作为参数进行传递。</p><pre><code class="language-text">..."actions": {  "visit": [    {      "{{#if /\\/(newest|show|ask)$/.test($jason.url) }}": {        "type": "$default"      }    },    {      "{{#else}}": {        "type": "$href",        "options": {          "url": "https://jasonette.github.io/Jasonpedia/webcontainer/agent/hijack.json",          "preload": {            "background": "#ffffff"          },          "options": {            "url": "{{$jason.url}}"          }        }      }    }  ]},</code></pre><p>在 <a href="https://link.zhihu.com/?target=https%3A//github.com/Jasonette/Jasonpedia/blob/gh-pages/webcontainer/agent/hijack.json" rel="nofollow noreferrer">此处</a> 查看 Web 浏览器的完整 JSON 标记（它仅有48行！）。</p><h3 id="--11">即时 “混合” 应用</h3><p>人们谈论“混合”应用时，通常是指封装在原生应用框架内的 HTML Web 应用程序。</p><p>但在此我并不是想说这个。我说的“混合”，是指真正的混合应用程序，即一个应用程序可以同时具有多个原生视图和多个基于 Web 的视图。与此同时，一个视图可以具有多个原生 UI 组件以及一个以相同的原生布局渲染的 Web 容器。</p><p><strong>基于 Web 的视图与原生视图之间的交互应是无缝的，你根本无法区分起始。</strong></p><p>在此例中，我创建了一个应用，它将 <a href="https://link.zhihu.com/?target=https%3A//www.jasonbase.com/" rel="nofollow noreferrer">jasonbase.com</a> 显示在 Web 容器中作为主视图。</p><p>Jasonbase 是我开发的一项免费 JSON 托管服务，旨在让你轻松托管 Jasonette 应用程序的 JSON 标记。</p><p>当然，它只是一个网站，但我已将其嵌入 Jasonette 中，所以当你点击链接时，它不会打开一个网页，而是会通过原生的 <code>$ href”</code> 过渡至原生的 JASON 视图。</p><p><strong>我完全无需触及</strong> <strong><a href="https://link.zhihu.com/?target=http%3A//jasonbase.com/" rel="nofollow noreferrer">Jasonbase.com</a></strong> <strong>的代码就可以构建此应用。</strong></p><p><strong>我只是将网站作为 Web 容器嵌入 Jasonette 中，并劫持了链接点击以实现本地处理，因此它可以执行诸如触发原生 API 和进行原生转换等所有原生操作。</strong></p><p>你可以在<a href="https://link.zhihu.com/?target=https%3A//github.com/Jasonette/Jasonpedia/blob/gh-pages/webcontainer/agent/hybrid.json" rel="nofollow noreferrer">此处</a>查看代码。</p><h3 id="--12">总结</h3><p>在我看来， <strong>一切都在框架级别得以应对</strong> ，这使得我们的工作非常出色。所有困难的工作都是在后台完成的。</p><p>不必给应用开发者施加重负，以从零开始实现以下的一切：</p><ul><li>将 Web 视图嵌入原生布局</li><li>创建 JavaScript 桥，以便应用程序可以在 Web 视图中进行函数调用</li><li>创建原生事件处理架构，以便 Web 视图可以在父应用中触发原生事件</li></ul><p>解决方案是创建一个包含以下内容的抽象：</p><ol><li><strong>声明式标记语言：</strong> 用于描述如何将 Web 视图嵌入原生应用</li><li><strong>通信协议 (JSON-RPC)：</strong> 用于应用与子 Web 视图间进行简单交互。</li></ol><p>我并不是说这就是解决所有问题的最终方案，但这对于我自己的用例而言是很出色的。</p><p>我尝试使用非常前沿的技术来构建应用程序，但该技术还没有稳定可靠的移动端实现（由于协议的本质，目前尚不清楚是否会有移动端的实现）。幸运的是，它具有 JavaScript 实现，因此我可以轻松地将其集成到应用程序中。</p><p>总而言之，这很棒且我对其感到满意。<a href="https://link.zhihu.com/?target=https%3A//docs.jasonette.com/web/" rel="nofollow noreferrer">文档是最新版的</a>，包含了所有新特性，欢迎你随时进行探索和体验。</p><blockquote>免责声明：能力越大，责任越大</blockquote><p>我想以免责声明结尾：尽管这种新技术功能强大，我认为你仍需要进行权衡，以构建具有出色用户体验的应用。</p><p>有些人可能会这样做并且仅使用 Web 视图来构建整个应用，但这样你的应用基本上就是一个网站，这违背了开发专属应用程序的本意。</p><p>我在此强调，我并不是说你应该始终使用 HTML 和原生组件来构建应用。我的意思是，这对于某些情况下的许多人非常有用。你需要自己把握分寸。</p><blockquote>学习更多</blockquote><p>Jasonette 原生核心和其子 Web 容器可以通过多种不同的配置进行通信，从而以强大的具有创造性的方式完成工作。这篇文章只是初步探索。</p><p>原文：<a href="https://www.freecodecamp.org/news/how-to-turn-your-website-into-a-mobile-app-with-7-lines-of-json-631c9c9895f5/">https://www.freecodecamp.org/news/how-to-turn-your-website-into-a-mobile-app-with-7-lines-of-json-631c9c9895f5/</a>，作者：Ethan</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
