虽然我使用 Jekyll 已经有一段时间了,但一直没注意到 Minimal Mistakes 这个主题的存在。

用它改版个人技术博客后,感觉效果还不错。我打算用这个主题把森罗社官网也重构一下。

用这篇博文简单总结一下改版期间遇到的一些问题,留作后续参考。

关于 Minimal Mistakes

它的官网使用的便是这个主题,视觉效果还行,适合 startup 用,简单省事。

网页吊顶(Masthead)

_data 文件夹下新建一个 navigation.yml 文件即可。

支持中文

_config.yml 中添加一条配置:

locale: "zh-CN"

拷贝ui-text.yml 文件至 _data 目录下。

(可以删除其他不需要的语言)

我本来以为主题的 data 文件也是能够被复用的,事实证明并非如此,需要手动拷贝到项目中去。

GA

老的 GA 即将线下,现在要使用 GTM。

免费开通 GTM 之后,会得到一个 tracking_id,然后在 _config.yml 文件中增加如下配置:

analytics:
  provider               : "google-gtag"
  google:
    tracking_id          : "YOUR-TRACKING-ID"
    anonymize_ip         : false # default

评论区

评论区的作用是增加 customer engagement

我一直使用 Disqus,这次也不例外。

然后继续在 _config.yml 文件中添加配置:

comments:
  provider               : "disqus"
  disqus:
    shortname            : 'feelang-github-io'

作者信息(Author)

Page 文件统一管理

Jekyll 官方文档的示例代码中,通常将 Page 文件放在根目录下,但是这么做会使得文件目录结构看起来比较乱,尤其是当页面变多之后。

比较好的办法是将这些 Page 文件放在一个文件夹下进行统一管理,实现步骤如下:

  1. 在根目录下新建一个 _pages 文件夹
  2. _config.yml 文件中新增一条配置:

     include:
       - _pages
    

这样 Page 文件就可以放到 _pages 文件夹中进行管理。

include 是 Jekyll 的一个配置命令:

Include: Force inclusion of directories and/or files in the conversion.

用法:include: [DIR, FILE, ...]

布局

这个主题提供了很多布局方式,这里只介绍几个常用的。

splash

splash 适用于 landing page,它继承自 default

这个主题的官网首页便是使用了这个布局。

下面简单分析下源码。

docs/_pages/home.mdFront Matter 定义了一个 header 变量:

header:
  overlay_color: "#5e616c"
  overlay_image: /assets/images/mm-home-page-feature.jpg
  actions:
    - label: "<i class='fas fa-download'></i> Install now"
      url: "/docs/quick-start-guide/"

这个变量在 _layouts/splash.html 中会被用到:


以上代码可以看出,这里既可以展示图片,也可以展示视频。

  • 展示图片的 include 文件是 page_hero.html

它用到的参数有:

  • page.header.overlay_image => overlay_img_path
  • page.header.overlay_color
  • page.header.overlay_filter => overlay_filter
    • gradient
    • rgba
  • page.header.image_description => image_description
  • page.header.show_overlay_excerpt
  • page.header.cta_url
  • page.header.actions
    • label
    • url
  • page.header.image
  • page.header.caption

展示视频的 include 文件是 _includes/page__hero-video.html









<!-- Courtesy of embedresponsively.com -->


这里进一步引用了 _include/video 这个模块:








<!-- Courtesy of embedresponsively.com -->

代码很简单,四个视频平台:

  • vimeo
  • youtube
  • google-drive
  • bilibili

继续分析 _layouts/splash.html 的剩余代码:

<div id="main" role="main">
  <article class="splash" itemscope itemtype="https://schema.org/CreativeWork">
    <meta itemprop="headline" content="Jekyll 主题 Minimal Mistakes 使用总结">
    <meta itemprop="description" content="虽然我使用 Jekyll 已经有一段时间了,但一直没注意到 Minimal Mistakes 这个主题的存在。">
    <meta itemprop="datePublished" content="2023-05-27T00:00:00+08:00">
    

    <section class="page__content" itemprop="text">
      





<div id="main" role="main">
  
  <div class="sidebar sticky">
  


<div itemscope itemtype="https://schema.org/Person" class="h-card">

  
    <div class="author__avatar">
      <a href="https://feelang.xyz/">
        <img src="/assets/images/site-logo.png" alt="feelang" itemprop="image" class="u-photo">
      </a>
    </div>
  

  <div class="author__content">
    <h3 class="author__name p-name" itemprop="name">
      <a class="u-url" rel="me" href="https://feelang.xyz/" itemprop="url">feelang</a>
    </h3>
    
      <div class="author__bio p-note" itemprop="description">
        <p>一个喜欢做知识类产品的自由职业者</p>

      </div>
    
  </div>

  <div class="author__urls-wrapper">
    <button class="btn btn--inverse">关注</button>
    <ul class="author__urls social-icons">
      
        <li itemprop="homeLocation" itemscope itemtype="https://schema.org/Place">
          <i class="fas fa-fw fa-map-marker-alt" aria-hidden="true"></i> <span itemprop="name" class="p-locality">杭州</span>
        </li>
      

      
        
          
            <li><a href="https://github.com/feelang" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fab fa-fw fa-github" aria-hidden="true"></i><span class="label">Github</span></a></li>
          
        
          
            <li><a href="https://weibo.com/u/1670598115" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fab fa-fw fa-weibo" aria-hidden="true"></i><span class="label">微博</span></a></li>
          
        
          
            <li><a href="https://blog.csdn.net/feelang" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fas fa-link" aria-hidden="true"></i><span class="label">CSDN</span></a></li>
          
        
          
            <li><a href="https://juejin.cn/user/2805609405883607" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fas fa-link" aria-hidden="true"></i><span class="label">掘金</span></a></li>
          
        
      

      

      
        <li>
          <a href="mailto:feelangcpp@gmail.com" rel="me" class="u-email">
            <meta itemprop="email" content="feelangcpp@gmail.com" />
            <i class="fas fa-fw fa-envelope-square" aria-hidden="true"></i><span class="label">电子邮箱</span>
          </a>
        </li>
      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      <!--
  <li>
    <a href="http://link-to-whatever-social-network.com/user/" itemprop="sameAs" rel="nofollow noopener noreferrer me">
      <i class="fas fa-fw" aria-hidden="true"></i> Custom Social Profile Link
    </a>
  </li>
-->
    </ul>
  </div>
</div>

  
  </div>



  <article class="page" itemscope itemtype="https://schema.org/CreativeWork">
    <meta itemprop="headline" content="Health 项目技术总结(Next.js + AntD)">
    <meta itemprop="description" content="好长一段时间没写前端,很多知识点都忘掉了,为了防止遗忘,趁着项目刚结束,赶紧总结一下。">
    <meta itemprop="datePublished" content="2023-05-18T00:00:00+08:00">
    

    <div class="page__inner-wrap">
      
        <header>
          <h1 id="page-title" class="page__title" itemprop="headline">
            <a href="https://feelang.xyz/programming/2023/05/18/summary-of-the-health-project.html" itemprop="url">Health 项目技术总结(Next.js + AntD)
</a>
          </h1>
          


        </header>
      

      <section class="page__content" itemprop="text">
        
          <aside class="sidebar__right sticky">
            <nav class="toc">
              <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> 目录</h4></header>
              <ul class="toc__menu"><li><a href="#邂逅-nextjs">邂逅 Next.js</a></li><li><a href="#老朋友-antd">老朋友 AntD</a><ul><li><a href="#使用-form">使用 Form</a></li><li><a href="#formitem-的联动校验">Form.Item 的联动校验</a></li></ul></li><li><a href="#typescript-tips">TypeScript tips</a><ul><li><a href="#string--number">string =&gt; number</a></li><li><a href="#interface">interface</a></li></ul></li></ul>
            </nav>
          </aside>
        
        <p>好长一段时间没写前端,很多知识点都忘掉了,为了防止遗忘,趁着项目刚结束,赶紧总结一下。</p>

<h2 id="邂逅-nextjs">邂逅 <code class="language-plaintext highlighter-rouge">Next.js</code></h2>

<p>这个项目用的是 <code class="language-plaintext highlighter-rouge">next.js</code>,也是 <code class="language-plaintext highlighter-rouge">react</code> 官方推荐的。今天才知道原来 react 已经升级改版,我之前掌握的技能早已过时。</p>

<ul>
  <li>React 官方推荐入口 =&gt; <a href="https://react.dev/learn/start-a-new-react-project">Start a New React Project</a></li>
  <li>Next.js 官网入口 =&gt; <a href="https://nextjs.org/">Next.js</a></li>
</ul>

<p>第一次用 <code class="language-plaintext highlighter-rouge">next.js</code>,不了解技术细节,不过从使用感受来说,还不错,用起来很方便。</p>

<p>从官网简介可以看出,这是一个全栈框架:</p>

<blockquote>
  <p>Next.js enables you to create full-stack Web applications by extending the latest React features, and integrating powerful Rust-based JavaScript tooling for the fastest builds.</p>
</blockquote>

<p>根据已经掌握的 <a href="https://ant.design/components">antd</a> 知识,很快就把第一个页面搞定了。</p>

<p>开始搞第二个页面时,我开始网上寻找 Router 解决方案,找到了 <a href="https://reactrouter.com/en/main">React Router</a>。</p>

<p>发现这个框架也已经升级改版,而我只掌握了上一个版本的用法,所以就开始翻文档,学习新用法,但是内容实在太长了,读不下去。</p>

<p>顺便发现了 <code class="language-plaintext highlighter-rouge">next.js</code> 自身内置了路由模块——<a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">API Routes</a>。</p>

<p>继续翻文档,结果云里雾里,根本看不懂。</p>

<p>灵机一动,不如去看看视频教程,找到了 <a href="https://www.youtube.com/watch?v=gSSsZReIFRk">Next.js App Router: Routing, Data Fetching, Caching</a>。</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/gSSsZReIFRk" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>原来在 <code class="language-plaintext highlighter-rouge">next.js</code> 项目中,只需要在 <code class="language-plaintext highlighter-rouge">app</code> 目录下新建一个文件夹就可以自动生成路由,实在方便。</p>

<p>就这样新建了第二个页面。</p>

<p>然后开始使用 <code class="language-plaintext highlighter-rouge">antd</code> 的 <code class="language-plaintext highlighter-rouge">Form</code> 组件写页面。</p>

<h2 id="老朋友-antd">老朋友 AntD</h2>

<h3 id="使用-form">使用 <code class="language-plaintext highlighter-rouge">Form</code></h3>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span>
    <span class="na">form</span><span class="p">=</span><span class="si">{</span><span class="nx">form</span><span class="si">}</span>
    <span class="na">layout</span><span class="p">=</span><span class="s">"vertical"</span>
    <span class="na">onFinish</span><span class="p">=</span><span class="si">{</span><span class="nx">onFinish</span><span class="si">}</span>
    <span class="na">onFinishFailed</span><span class="p">=</span><span class="si">{</span><span class="nx">onFinishFailed</span><span class="si">}</span>
<span class="p">&gt;</span>
</code></pre></div></div>

<p>其中:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">form={form}</code> 的 <code class="language-plaintext highlighter-rouge">form</code> 来自:<code class="language-plaintext highlighter-rouge">const [form] = Form.useForm()</code></li>
  <li><code class="language-plaintext highlighter-rouge">onFinish</code> 和 <code class="language-plaintext highlighter-rouge">onFinishFailed</code> 是两个函数</li>
</ul>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">const</span> <span class="nx">onFinish</span> <span class="o">=</span> <span class="p">(</span><span class="nx">values</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">values</span><span class="p">)</span>
 <span class="p">}</span>

 <span class="kd">const</span> <span class="nx">onFinishFailed</span> <span class="o">=</span> <span class="p">(</span><span class="nx">errorInfo</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">errorInfo</span><span class="p">)</span>
 <span class="p">}</span>
</code></pre></div></div>

<p>页面底部添加两个按钮:计算 &amp; 重置。</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Space</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">type</span><span class="p">=</span><span class="s">"primary"</span> <span class="na">htmlType</span><span class="p">=</span><span class="s">"submit"</span><span class="p">&gt;</span>计算<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">htmlType</span><span class="p">=</span><span class="s">"reset"</span><span class="p">&gt;</span>重置<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Space</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>其中 <code class="language-plaintext highlighter-rouge">&lt;Space&gt;</code> 一用,就不用单独设置边距了,非常方便。</p>

<p>这两个按钮的功能通过设置 <code class="language-plaintext highlighter-rouge">htmlType</code> 属性来实现,其他任何代码不需要。</p>

<h3 id="formitem-的联动校验"><code class="language-plaintext highlighter-rouge">Form.Item</code> 的联动校验</h3>

<p>其中一个输入框要填写年龄,且是必填:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span> <span class="na">label</span><span class="p">=</span><span class="s">"1 年龄(age)"</span> <span class="na">name</span><span class="p">=</span><span class="s">"age"</span>
    <span class="na">rules</span><span class="p">=</span><span class="si">{</span><span class="p">[{</span> <span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请输入年龄</span><span class="dl">'</span> <span class="p">}]</span><span class="si">}</span>
<span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">InputNumber</span> <span class="na">placeholder</span><span class="p">=</span><span class="s">"请输入年龄"</span> <span class="na">min</span><span class="p">=</span><span class="si">{</span><span class="mi">18</span><span class="si">}</span> <span class="na">max</span><span class="p">=</span><span class="si">{</span><span class="mi">100</span><span class="si">}</span>
        <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">setAge</span><span class="si">}</span>
    <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>利用 <code class="language-plaintext highlighter-rouge">setAge</code> 接收输入值:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">age</span><span class="p">,</span> <span class="nx">setAge</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">)</span>
</code></pre></div></div>

<blockquote>
  <p>需要留意的是,<code class="language-plaintext highlighter-rouge">InputNumber</code> 的 <code class="language-plaintext highlighter-rouge">onChange</code> 属性的变量类型为 <code class="language-plaintext highlighter-rouge">string | number | null</code>。</p>
</blockquote>

<p>第二个输入框也是年龄,但它的输入值必须小于 <code class="language-plaintext highlighter-rouge">age</code>:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span> <span class="na">label</span><span class="p">=</span><span class="s">"3.1 绝经年龄(age at menopause)"</span>
           <span class="na">name</span><span class="p">=</span><span class="s">"menopauseAge"</span>
           <span class="na">dependencies</span><span class="p">=</span><span class="si">{</span><span class="p">[</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">]</span><span class="si">}</span>
           <span class="na">rules</span><span class="p">=</span><span class="si">{</span><span class="p">[</span>
               <span class="p">{</span>
                   <span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
                   <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请输入年龄</span><span class="dl">'</span><span class="p">,</span>
               <span class="p">},</span>
               <span class="p">({</span> <span class="nx">getFieldValue</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">({</span>
                   <span class="nx">validator</span><span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                       <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span> <span class="o">||</span> <span class="nx">getFieldValue</span><span class="p">(</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                           <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">();</span>
                       <span class="p">}</span>
                       <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">reject</span><span class="p">(</span><span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`绝经年龄不得大于当前年龄(</span><span class="p">${</span><span class="nx">age</span><span class="p">}</span><span class="s2">)`</span><span class="p">));</span>
                   <span class="p">},</span>
               <span class="p">}),</span>
           <span class="p">]</span><span class="si">}</span>
<span class="p">&gt;</span>
</code></pre></div></div>

<p>这个用法值得仔细记录一下。</p>

<p>先看 <code class="language-plaintext highlighter-rouge">dependencies</code> 的用法,官网介绍如下,简单明了。</p>

<blockquote>
  <p>当字段间存在依赖关系时使用。如果一个字段设置了 dependencies 属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。
一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 dependencies 后,“密码”字段更新会重新触发“校验密码”的校验逻辑。</p>
</blockquote>

<p>再看 <code class="language-plaintext highlighter-rouge">rules</code> 用法:</p>

<blockquote>
  <p>校验规则,设置字段的校验逻辑。</p>
</blockquote>

<p>类型为 <code class="language-plaintext highlighter-rouge">Rule[]</code>,是个数组。</p>

<p><code class="language-plaintext highlighter-rouge">Rule</code> 定义如下:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Rule</span> <span class="o">=</span> <span class="nx">RuleConfig</span> <span class="o">|</span> <span class="p">((</span><span class="nx">form</span><span class="p">:</span> <span class="nx">FormInstance</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">RuleConfig</span><span class="p">);</span>
</code></pre></div></div>

<p>可以看出,<code class="language-plaintext highlighter-rouge">Rule</code> 支持两种类型,所以上面代码中 <code class="language-plaintext highlighter-rouge">rules</code> 的第一个元素类型为 <code class="language-plaintext highlighter-rouge">RuleConfig</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
    <span class="nl">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="nx">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请输入年龄</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
</code></pre></div></div>

<p>第二个元素类型为 <code class="language-plaintext highlighter-rouge">((form: FormInstance) =&gt; RuleConfig)</code>,是个高阶函数。</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">({</span> <span class="nx">getFieldValue</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">({</span>
    <span class="nx">validator</span><span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span> <span class="o">||</span> <span class="nx">getFieldValue</span><span class="p">(</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">reject</span><span class="p">(</span><span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`绝经年龄不得大于当前年龄(</span><span class="p">${</span><span class="nx">age</span><span class="p">}</span><span class="s2">)`</span><span class="p">));</span>
    <span class="p">},</span>
<span class="p">}),</span>
</code></pre></div></div>

<p>由此可以推测出 <code class="language-plaintext highlighter-rouge">getFieldValue</code> 是 <code class="language-plaintext highlighter-rouge">FormInstance</code> 的一个属性,通过看源码可以证实:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">FormInstance</span><span class="o">&lt;</span><span class="nx">Values</span> <span class="o">=</span> <span class="kr">any</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="na">getFieldValue</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">StoreValue</span><span class="p">;</span>
    <span class="nl">getFieldsValue</span><span class="p">:</span> <span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Values</span><span class="p">)</span> <span class="o">&amp;</span> <span class="p">((</span><span class="na">nameList</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">[]</span> <span class="o">|</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">filterFunc</span><span class="p">?:</span> <span class="p">(</span><span class="na">meta</span><span class="p">:</span> <span class="nx">Meta</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">any</span><span class="p">);</span>
    <span class="nl">getFieldError</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">string</span><span class="p">[];</span>
    <span class="nl">getFieldsError</span><span class="p">:</span> <span class="p">(</span><span class="nx">nameList</span><span class="p">?:</span> <span class="nx">NamePath</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="nx">FieldError</span><span class="p">[];</span>
    <span class="nl">getFieldWarning</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">string</span><span class="p">[];</span>
    <span class="nl">isFieldsTouched</span><span class="p">:</span> <span class="p">((</span><span class="nx">nameList</span><span class="p">?:</span> <span class="nx">NamePath</span><span class="p">[],</span> <span class="nx">allFieldsTouched</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">&amp;</span> <span class="p">((</span><span class="nx">allFieldsTouched</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">);</span>
    <span class="nl">isFieldTouched</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="nl">isFieldValidating</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="nl">isFieldsValidating</span><span class="p">:</span> <span class="p">(</span><span class="na">nameList</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="nl">resetFields</span><span class="p">:</span> <span class="p">(</span><span class="nx">fields</span><span class="p">?:</span> <span class="nx">NamePath</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">setFields</span><span class="p">:</span> <span class="p">(</span><span class="na">fields</span><span class="p">:</span> <span class="nx">FieldData</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">setFieldValue</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">,</span> <span class="na">value</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">setFieldsValue</span><span class="p">:</span> <span class="p">(</span><span class="na">values</span><span class="p">:</span> <span class="nx">RecursivePartial</span><span class="o">&lt;</span><span class="nx">Values</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">validateFields</span><span class="p">:</span> <span class="nx">ValidateFields</span><span class="o">&lt;</span><span class="nx">Values</span><span class="o">&gt;</span><span class="p">;</span>
    <span class="nl">submit</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">validator</code> 也是一个高阶函数:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Validator</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rule</span><span class="p">:</span> <span class="nx">RuleObject</span><span class="p">,</span> <span class="nx">value</span><span class="p">:</span> <span class="nx">StoreValue</span><span class="p">,</span> <span class="nx">callback</span><span class="p">:</span> <span class="p">(</span><span class="nx">error</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span> <span class="o">|</span> <span class="kr">any</span><span class="o">&gt;</span> <span class="o">|</span> <span class="k">void</span><span class="p">;</span>
</code></pre></div></div>

<p>返回值是一个 <code class="language-plaintext highlighter-rouge">Promise</code> 或 <code class="language-plaintext highlighter-rouge">void</code>。</p>

<p>通过以上分析,可以总结出:当两个 <code class="language-plaintext highlighter-rouge">Form.Item</code> 之间存在依赖时,可通过 <code class="language-plaintext highlighter-rouge">dependencies</code> 和 <code class="language-plaintext highlighter-rouge">rules</code> 属性设定依赖逻辑。</p>

<h2 id="typescript-tips">TypeScript tips</h2>

<h3 id="string--number">string =&gt; number</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// menopauseAge 是个 `string`</span>
<span class="kd">const</span> <span class="nx">menopauseAge</span> <span class="o">=</span> <span class="nx">values</span><span class="p">.</span><span class="nx">menopauseAge</span> <span class="p">?</span> <span class="o">+</span><span class="nx">values</span><span class="p">.</span><span class="nx">menopauseAge</span> <span class="p">:</span> <span class="mi">0</span>
</code></pre></div></div>

<h3 id="interface">interface</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">RiskResult</span> <span class="p">{</span>
    <span class="nl">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="kd">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">success</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">warning</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">info</span><span class="dl">"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>用法:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">results</span><span class="p">:</span> <span class="nx">RiskResult</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">乳腺癌低风险</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请继续保持健康生活方式</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span>
    <span class="p">},</span>
<span class="p">]</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">result</span><span class="p">,</span> <span class="nx">setResult</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">RiskResult</span><span class="o">&gt;</span><span class="p">()</span>
</code></pre></div></div>


        
      </section>

      <footer class="page__meta">
        
        


        

  <p class="page__date"><strong><i class="fas fa-fw fa-calendar-alt" aria-hidden="true"></i> 更新时间:</strong> <time class="dt-published" datetime="2023-05-18T00:00:00+08:00">May 18, 2023</time></p>

      </footer>

      

      
  <nav class="pagination">
    
      <a href="/programming/2022/10/14/difference-between-gulp-and-webpack.html" class="pagination--pager" title="Gulp 和 Webpack 有什么区别?
">上一页</a>
    
    
      <a href="/tools/2023/05/27/jekyll-theme-minimal-mistakes.html" class="pagination--pager" title="Jekyll 主题 Minimal Mistakes 使用总结
">下一页</a>
    
  </nav>

    </div>

    
      <div class="page__comments">
  
  
      <h4 class="page__comments-title">留下评论</h4>
      <section id="disqus_thread"></section>
    
</div>

    
  </article>

  
  
</div>

    </section>
  </article>
</div>

这部分代码只是给 article 元素中添加了四个 meta

Page Variable Item Property
page.title headline
page.excerpt description
page.date datePublished
page.last_modified_at dateModified

对页面视觉没有作用,应该只是为了 SEO。

default

_layouts/default.html 是所有布局文件的基类,它定了 html 文件的基本框架。

通过源码可以看出,<html> 标签中有 lang 的属性:

<html lang="zh" class="no-js">

然后是 <head> 标签:

<head>
  <meta charset="utf-8">

<!-- begin _includes/seo.html --><title>Jekyll 主题 Minimal Mistakes 使用总结 - feelang的技术博客</title>
<meta name="description" content="虽然我使用 Jekyll 已经有一段时间了,但一直没注意到 Minimal Mistakes 这个主题的存在。">


  <meta name="author" content="feelang">
  
  <meta property="article:author" content="feelang">
  


<meta property="og:type" content="article">
<meta property="og:locale" content="zh_CN">
<meta property="og:site_name" content="feelang的技术博客">
<meta property="og:title" content="Jekyll 主题 Minimal Mistakes 使用总结">
<meta property="og:url" content="https://feelang.xyz/tools/2023/05/27/jekyll-theme-minimal-mistakes.html">


  <meta property="og:description" content="虽然我使用 Jekyll 已经有一段时间了,但一直没注意到 Minimal Mistakes 这个主题的存在。">



  <meta property="og:image" content="https://feelang.xyz/assets/images/site-logo.png">





  <meta property="article:published_time" content="2023-05-27T00:00:00+08:00">






<link rel="canonical" href="https://feelang.xyz/tools/2023/05/27/jekyll-theme-minimal-mistakes.html">












<!-- end _includes/seo.html -->



  <link href="/feed.xml" type="application/atom+xml" rel="alternate" title="feelang的技术博客 Feed">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<script type="text/javascript">
  document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/g, '') + ' js ';
  
</script>

<!-- For all browsers -->
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="preload" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@latest/css/all.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@latest/css/all.min.css"></noscript>



  <!-- start custom head snippets -->

<!-- insert favicons. use https://realfavicongenerator.net/ -->

<!-- end custom head snippets -->

</head>

head.html 中做了以下几件事:

  • seo
  • atom feed
  • viewport
  • stylesheet => /assets/css/main.css
  • fontawesome

还支持自定义 js:


head/custom.html 是留给我们自定义用的。

继续看 <body> 标签:

<nav class="skip-links">
  <ul>
    <li><a href="#site-nav" class="screen-reader-shortcut">转到主导航栏</a></li>
    <li><a href="#main" class="screen-reader-shortcut">转到内容</a></li>
    <li><a href="#footer" class="screen-reader-shortcut">转到底部</a></li>
  </ul>
</nav>



<div class="masthead">
  <div class="masthead__inner-wrap">
    <div class="masthead__menu">
      <nav id="site-nav" class="greedy-nav">
        
        <a class="site-title" href="/">
          feelang的技术博客
          <span class="site-subtitle">分享全栈开发知识</span>
        </a>
        <ul class="visible-links"><li class="masthead__menu-item">
              <a
                href="/weapps/"
                
                
              >小程序</a>
            </li><li class="masthead__menu-item">
              <a
                href="/apps/"
                
                
              >APP</a>
            </li><li class="masthead__menu-item">
              <a
                href="/web/"
                
                
              >Web</a>
            </li><li class="masthead__menu-item">
              <a
                href="/tutorials/"
                
                
              >教程</a>
            </li><li class="masthead__menu-item">
              <a
                href="/about/"
                
                
              >关于</a>
            </li></ul>
        
        <button class="search__toggle" type="button">
          <span class="visually-hidden">切换搜索</span>
          <i class="fas fa-search"></i>
        </button>
        
        <button class="greedy-nav__toggle hidden" type="button">
          <span class="visually-hidden">切换菜单</span>
          <div class="navicon"></div>
        </button>
        <ul class="hidden-links hidden"></ul>
      </nav>
    </div>
  </div>
</div>

先看 _includes/skip-links.html

<nav class="skip-links">
  <ul>
    <li><a href="#site-nav" class="screen-reader-shortcut">转到主导航栏</a></li>
    <li><a href="#main" class="screen-reader-shortcut">转到内容</a></li>
    <li><a href="#footer" class="screen-reader-shortcut">转到底部</a></li>
  </ul>
</nav>

页面内快速跳转用的。

再来看 _includes/masthead.html,页面吊顶:


获取 _config.yml 中配置的 site.logo,并将其赋值给 logo_path 变量。

用一个 <a> 标签做展示:


标题支持 titlesubtitle

<a class="site-title" href="/">
  feelang的技术博客
  <span class="site-subtitle">分享全栈开发知识</span>
</a>

然后展示导航条:

<ul class="visible-links"><li class="masthead__menu-item">
      <a href="/weapps/">小程序</a>
    </li><li class="masthead__menu-item">
      <a href="/apps/">APP</a>
    </li><li class="masthead__menu-item">
      <a href="/web/">Web</a>
    </li><li class="masthead__menu-item">
      <a href="/tutorials/">教程</a>
    </li><li class="masthead__menu-item">
      <a href="/about/">关于</a>
    </li></ul>

接下来是搜索按钮:


<button class="search__toggle" type="button">
  <span class="visually-hidden">切换搜索</span>
  <i class="fas fa-search"></i>
</button>

最后是自适应布局中的 Toggle menu

<button class="greedy-nav__toggle hidden" type="button">
  <span class="visually-hidden">切换菜单</span>
  <div class="navicon"></div>
</button>

以上便是 _includes/masthead.html 的源码,回到 _layouts/default.html 继续往下看:

子类布局占位:

<div class="initial-content">
  





<div id="main" role="main">
  
  <div class="sidebar sticky">
  


<div itemscope itemtype="https://schema.org/Person" class="h-card">

  
    <div class="author__avatar">
      <a href="https://feelang.xyz/">
        <img src="/assets/images/site-logo.png" alt="feelang" itemprop="image" class="u-photo">
      </a>
    </div>
  

  <div class="author__content">
    <h3 class="author__name p-name" itemprop="name">
      <a class="u-url" rel="me" href="https://feelang.xyz/" itemprop="url">feelang</a>
    </h3>
    
      <div class="author__bio p-note" itemprop="description">
        <p>一个喜欢做知识类产品的自由职业者</p>

      </div>
    
  </div>

  <div class="author__urls-wrapper">
    <button class="btn btn--inverse">关注</button>
    <ul class="author__urls social-icons">
      
        <li itemprop="homeLocation" itemscope itemtype="https://schema.org/Place">
          <i class="fas fa-fw fa-map-marker-alt" aria-hidden="true"></i> <span itemprop="name" class="p-locality">杭州</span>
        </li>
      

      
        
          
            <li><a href="https://github.com/feelang" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fab fa-fw fa-github" aria-hidden="true"></i><span class="label">Github</span></a></li>
          
        
          
            <li><a href="https://weibo.com/u/1670598115" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fab fa-fw fa-weibo" aria-hidden="true"></i><span class="label">微博</span></a></li>
          
        
          
            <li><a href="https://blog.csdn.net/feelang" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fas fa-link" aria-hidden="true"></i><span class="label">CSDN</span></a></li>
          
        
          
            <li><a href="https://juejin.cn/user/2805609405883607" rel="nofollow noopener noreferrer me" itemprop="sameAs"><i class="fas fa-link" aria-hidden="true"></i><span class="label">掘金</span></a></li>
          
        
      

      

      
        <li>
          <a href="mailto:feelangcpp@gmail.com" rel="me" class="u-email">
            <meta itemprop="email" content="feelangcpp@gmail.com" />
            <i class="fas fa-fw fa-envelope-square" aria-hidden="true"></i><span class="label">电子邮箱</span>
          </a>
        </li>
      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      

      <!--
  <li>
    <a href="http://link-to-whatever-social-network.com/user/" itemprop="sameAs" rel="nofollow noopener noreferrer me">
      <i class="fas fa-fw" aria-hidden="true"></i> Custom Social Profile Link
    </a>
  </li>
-->
    </ul>
  </div>
</div>

  
  </div>



  <article class="page" itemscope itemtype="https://schema.org/CreativeWork">
    <meta itemprop="headline" content="Health 项目技术总结(Next.js + AntD)">
    <meta itemprop="description" content="好长一段时间没写前端,很多知识点都忘掉了,为了防止遗忘,趁着项目刚结束,赶紧总结一下。">
    <meta itemprop="datePublished" content="2023-05-18T00:00:00+08:00">
    

    <div class="page__inner-wrap">
      
        <header>
          <h1 id="page-title" class="page__title" itemprop="headline">
            <a href="https://feelang.xyz/programming/2023/05/18/summary-of-the-health-project.html" itemprop="url">Health 项目技术总结(Next.js + AntD)
</a>
          </h1>
          


        </header>
      

      <section class="page__content" itemprop="text">
        
          <aside class="sidebar__right sticky">
            <nav class="toc">
              <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> 目录</h4></header>
              <ul class="toc__menu"><li><a href="#邂逅-nextjs">邂逅 Next.js</a></li><li><a href="#老朋友-antd">老朋友 AntD</a><ul><li><a href="#使用-form">使用 Form</a></li><li><a href="#formitem-的联动校验">Form.Item 的联动校验</a></li></ul></li><li><a href="#typescript-tips">TypeScript tips</a><ul><li><a href="#string--number">string =&gt; number</a></li><li><a href="#interface">interface</a></li></ul></li></ul>
            </nav>
          </aside>
        
        <p>好长一段时间没写前端,很多知识点都忘掉了,为了防止遗忘,趁着项目刚结束,赶紧总结一下。</p>

<h2 id="邂逅-nextjs">邂逅 <code class="language-plaintext highlighter-rouge">Next.js</code></h2>

<p>这个项目用的是 <code class="language-plaintext highlighter-rouge">next.js</code>,也是 <code class="language-plaintext highlighter-rouge">react</code> 官方推荐的。今天才知道原来 react 已经升级改版,我之前掌握的技能早已过时。</p>

<ul>
  <li>React 官方推荐入口 =&gt; <a href="https://react.dev/learn/start-a-new-react-project">Start a New React Project</a></li>
  <li>Next.js 官网入口 =&gt; <a href="https://nextjs.org/">Next.js</a></li>
</ul>

<p>第一次用 <code class="language-plaintext highlighter-rouge">next.js</code>,不了解技术细节,不过从使用感受来说,还不错,用起来很方便。</p>

<p>从官网简介可以看出,这是一个全栈框架:</p>

<blockquote>
  <p>Next.js enables you to create full-stack Web applications by extending the latest React features, and integrating powerful Rust-based JavaScript tooling for the fastest builds.</p>
</blockquote>

<p>根据已经掌握的 <a href="https://ant.design/components">antd</a> 知识,很快就把第一个页面搞定了。</p>

<p>开始搞第二个页面时,我开始网上寻找 Router 解决方案,找到了 <a href="https://reactrouter.com/en/main">React Router</a>。</p>

<p>发现这个框架也已经升级改版,而我只掌握了上一个版本的用法,所以就开始翻文档,学习新用法,但是内容实在太长了,读不下去。</p>

<p>顺便发现了 <code class="language-plaintext highlighter-rouge">next.js</code> 自身内置了路由模块——<a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">API Routes</a>。</p>

<p>继续翻文档,结果云里雾里,根本看不懂。</p>

<p>灵机一动,不如去看看视频教程,找到了 <a href="https://www.youtube.com/watch?v=gSSsZReIFRk">Next.js App Router: Routing, Data Fetching, Caching</a>。</p>

<!-- Courtesy of embedresponsively.com -->

<div class="responsive-video-container">
    <iframe src="https://www.youtube-nocookie.com/embed/gSSsZReIFRk" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
  </div>

<p>原来在 <code class="language-plaintext highlighter-rouge">next.js</code> 项目中,只需要在 <code class="language-plaintext highlighter-rouge">app</code> 目录下新建一个文件夹就可以自动生成路由,实在方便。</p>

<p>就这样新建了第二个页面。</p>

<p>然后开始使用 <code class="language-plaintext highlighter-rouge">antd</code> 的 <code class="language-plaintext highlighter-rouge">Form</code> 组件写页面。</p>

<h2 id="老朋友-antd">老朋友 AntD</h2>

<h3 id="使用-form">使用 <code class="language-plaintext highlighter-rouge">Form</code></h3>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span>
    <span class="na">form</span><span class="p">=</span><span class="si">{</span><span class="nx">form</span><span class="si">}</span>
    <span class="na">layout</span><span class="p">=</span><span class="s">"vertical"</span>
    <span class="na">onFinish</span><span class="p">=</span><span class="si">{</span><span class="nx">onFinish</span><span class="si">}</span>
    <span class="na">onFinishFailed</span><span class="p">=</span><span class="si">{</span><span class="nx">onFinishFailed</span><span class="si">}</span>
<span class="p">&gt;</span>
</code></pre></div></div>

<p>其中:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">form={form}</code> 的 <code class="language-plaintext highlighter-rouge">form</code> 来自:<code class="language-plaintext highlighter-rouge">const [form] = Form.useForm()</code></li>
  <li><code class="language-plaintext highlighter-rouge">onFinish</code> 和 <code class="language-plaintext highlighter-rouge">onFinishFailed</code> 是两个函数</li>
</ul>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">const</span> <span class="nx">onFinish</span> <span class="o">=</span> <span class="p">(</span><span class="nx">values</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">values</span><span class="p">)</span>
 <span class="p">}</span>

 <span class="kd">const</span> <span class="nx">onFinishFailed</span> <span class="o">=</span> <span class="p">(</span><span class="nx">errorInfo</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">errorInfo</span><span class="p">)</span>
 <span class="p">}</span>
</code></pre></div></div>

<p>页面底部添加两个按钮:计算 &amp; 重置。</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">Space</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">type</span><span class="p">=</span><span class="s">"primary"</span> <span class="na">htmlType</span><span class="p">=</span><span class="s">"submit"</span><span class="p">&gt;</span>计算<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">htmlType</span><span class="p">=</span><span class="s">"reset"</span><span class="p">&gt;</span>重置<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nc">Space</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>其中 <code class="language-plaintext highlighter-rouge">&lt;Space&gt;</code> 一用,就不用单独设置边距了,非常方便。</p>

<p>这两个按钮的功能通过设置 <code class="language-plaintext highlighter-rouge">htmlType</code> 属性来实现,其他任何代码不需要。</p>

<h3 id="formitem-的联动校验"><code class="language-plaintext highlighter-rouge">Form.Item</code> 的联动校验</h3>

<p>其中一个输入框要填写年龄,且是必填:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span> <span class="na">label</span><span class="p">=</span><span class="s">"1 年龄(age)"</span> <span class="na">name</span><span class="p">=</span><span class="s">"age"</span>
    <span class="na">rules</span><span class="p">=</span><span class="si">{</span><span class="p">[{</span> <span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请输入年龄</span><span class="dl">'</span> <span class="p">}]</span><span class="si">}</span>
<span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nc">InputNumber</span> <span class="na">placeholder</span><span class="p">=</span><span class="s">"请输入年龄"</span> <span class="na">min</span><span class="p">=</span><span class="si">{</span><span class="mi">18</span><span class="si">}</span> <span class="na">max</span><span class="p">=</span><span class="si">{</span><span class="mi">100</span><span class="si">}</span>
        <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">setAge</span><span class="si">}</span>
    <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>利用 <code class="language-plaintext highlighter-rouge">setAge</code> 接收输入值:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">age</span><span class="p">,</span> <span class="nx">setAge</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">)</span>
</code></pre></div></div>

<blockquote>
  <p>需要留意的是,<code class="language-plaintext highlighter-rouge">InputNumber</code> 的 <code class="language-plaintext highlighter-rouge">onChange</code> 属性的变量类型为 <code class="language-plaintext highlighter-rouge">string | number | null</code>。</p>
</blockquote>

<p>第二个输入框也是年龄,但它的输入值必须小于 <code class="language-plaintext highlighter-rouge">age</code>:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Item</span> <span class="na">label</span><span class="p">=</span><span class="s">"3.1 绝经年龄(age at menopause)"</span>
           <span class="na">name</span><span class="p">=</span><span class="s">"menopauseAge"</span>
           <span class="na">dependencies</span><span class="p">=</span><span class="si">{</span><span class="p">[</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">]</span><span class="si">}</span>
           <span class="na">rules</span><span class="p">=</span><span class="si">{</span><span class="p">[</span>
               <span class="p">{</span>
                   <span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
                   <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请输入年龄</span><span class="dl">'</span><span class="p">,</span>
               <span class="p">},</span>
               <span class="p">({</span> <span class="nx">getFieldValue</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">({</span>
                   <span class="nx">validator</span><span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                       <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span> <span class="o">||</span> <span class="nx">getFieldValue</span><span class="p">(</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
                           <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">();</span>
                       <span class="p">}</span>
                       <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">reject</span><span class="p">(</span><span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`绝经年龄不得大于当前年龄(</span><span class="p">${</span><span class="nx">age</span><span class="p">}</span><span class="s2">)`</span><span class="p">));</span>
                   <span class="p">},</span>
               <span class="p">}),</span>
           <span class="p">]</span><span class="si">}</span>
<span class="p">&gt;</span>
</code></pre></div></div>

<p>这个用法值得仔细记录一下。</p>

<p>先看 <code class="language-plaintext highlighter-rouge">dependencies</code> 的用法,官网介绍如下,简单明了。</p>

<blockquote>
  <p>当字段间存在依赖关系时使用。如果一个字段设置了 dependencies 属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。
一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 dependencies 后,“密码”字段更新会重新触发“校验密码”的校验逻辑。</p>
</blockquote>

<p>再看 <code class="language-plaintext highlighter-rouge">rules</code> 用法:</p>

<blockquote>
  <p>校验规则,设置字段的校验逻辑。</p>
</blockquote>

<p>类型为 <code class="language-plaintext highlighter-rouge">Rule[]</code>,是个数组。</p>

<p><code class="language-plaintext highlighter-rouge">Rule</code> 定义如下:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Rule</span> <span class="o">=</span> <span class="nx">RuleConfig</span> <span class="o">|</span> <span class="p">((</span><span class="nx">form</span><span class="p">:</span> <span class="nx">FormInstance</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">RuleConfig</span><span class="p">);</span>
</code></pre></div></div>

<p>可以看出,<code class="language-plaintext highlighter-rouge">Rule</code> 支持两种类型,所以上面代码中 <code class="language-plaintext highlighter-rouge">rules</code> 的第一个元素类型为 <code class="language-plaintext highlighter-rouge">RuleConfig</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
    <span class="nl">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="nx">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请输入年龄</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
</code></pre></div></div>

<p>第二个元素类型为 <code class="language-plaintext highlighter-rouge">((form: FormInstance) =&gt; RuleConfig)</code>,是个高阶函数。</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">({</span> <span class="nx">getFieldValue</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">({</span>
    <span class="nx">validator</span><span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span> <span class="o">||</span> <span class="nx">getFieldValue</span><span class="p">(</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">reject</span><span class="p">(</span><span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`绝经年龄不得大于当前年龄(</span><span class="p">${</span><span class="nx">age</span><span class="p">}</span><span class="s2">)`</span><span class="p">));</span>
    <span class="p">},</span>
<span class="p">}),</span>
</code></pre></div></div>

<p>由此可以推测出 <code class="language-plaintext highlighter-rouge">getFieldValue</code> 是 <code class="language-plaintext highlighter-rouge">FormInstance</code> 的一个属性,通过看源码可以证实:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">FormInstance</span><span class="o">&lt;</span><span class="nx">Values</span> <span class="o">=</span> <span class="kr">any</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="na">getFieldValue</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">StoreValue</span><span class="p">;</span>
    <span class="nl">getFieldsValue</span><span class="p">:</span> <span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Values</span><span class="p">)</span> <span class="o">&amp;</span> <span class="p">((</span><span class="na">nameList</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">[]</span> <span class="o">|</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">filterFunc</span><span class="p">?:</span> <span class="p">(</span><span class="na">meta</span><span class="p">:</span> <span class="nx">Meta</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">any</span><span class="p">);</span>
    <span class="nl">getFieldError</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">string</span><span class="p">[];</span>
    <span class="nl">getFieldsError</span><span class="p">:</span> <span class="p">(</span><span class="nx">nameList</span><span class="p">?:</span> <span class="nx">NamePath</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="nx">FieldError</span><span class="p">[];</span>
    <span class="nl">getFieldWarning</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">string</span><span class="p">[];</span>
    <span class="nl">isFieldsTouched</span><span class="p">:</span> <span class="p">((</span><span class="nx">nameList</span><span class="p">?:</span> <span class="nx">NamePath</span><span class="p">[],</span> <span class="nx">allFieldsTouched</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">&amp;</span> <span class="p">((</span><span class="nx">allFieldsTouched</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">);</span>
    <span class="nl">isFieldTouched</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="nl">isFieldValidating</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="nl">isFieldsValidating</span><span class="p">:</span> <span class="p">(</span><span class="na">nameList</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="nl">resetFields</span><span class="p">:</span> <span class="p">(</span><span class="nx">fields</span><span class="p">?:</span> <span class="nx">NamePath</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">setFields</span><span class="p">:</span> <span class="p">(</span><span class="na">fields</span><span class="p">:</span> <span class="nx">FieldData</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">setFieldValue</span><span class="p">:</span> <span class="p">(</span><span class="na">name</span><span class="p">:</span> <span class="nx">NamePath</span><span class="p">,</span> <span class="na">value</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">setFieldsValue</span><span class="p">:</span> <span class="p">(</span><span class="na">values</span><span class="p">:</span> <span class="nx">RecursivePartial</span><span class="o">&lt;</span><span class="nx">Values</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
    <span class="nl">validateFields</span><span class="p">:</span> <span class="nx">ValidateFields</span><span class="o">&lt;</span><span class="nx">Values</span><span class="o">&gt;</span><span class="p">;</span>
    <span class="nl">submit</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">validator</code> 也是一个高阶函数:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Validator</span> <span class="o">=</span> <span class="p">(</span><span class="nx">rule</span><span class="p">:</span> <span class="nx">RuleObject</span><span class="p">,</span> <span class="nx">value</span><span class="p">:</span> <span class="nx">StoreValue</span><span class="p">,</span> <span class="nx">callback</span><span class="p">:</span> <span class="p">(</span><span class="nx">error</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span> <span class="o">|</span> <span class="kr">any</span><span class="o">&gt;</span> <span class="o">|</span> <span class="k">void</span><span class="p">;</span>
</code></pre></div></div>

<p>返回值是一个 <code class="language-plaintext highlighter-rouge">Promise</code> 或 <code class="language-plaintext highlighter-rouge">void</code>。</p>

<p>通过以上分析,可以总结出:当两个 <code class="language-plaintext highlighter-rouge">Form.Item</code> 之间存在依赖时,可通过 <code class="language-plaintext highlighter-rouge">dependencies</code> 和 <code class="language-plaintext highlighter-rouge">rules</code> 属性设定依赖逻辑。</p>

<h2 id="typescript-tips">TypeScript tips</h2>

<h3 id="string--number">string =&gt; number</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// menopauseAge 是个 `string`</span>
<span class="kd">const</span> <span class="nx">menopauseAge</span> <span class="o">=</span> <span class="nx">values</span><span class="p">.</span><span class="nx">menopauseAge</span> <span class="p">?</span> <span class="o">+</span><span class="nx">values</span><span class="p">.</span><span class="nx">menopauseAge</span> <span class="p">:</span> <span class="mi">0</span>
</code></pre></div></div>

<h3 id="interface">interface</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">RiskResult</span> <span class="p">{</span>
    <span class="nl">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="kd">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">success</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">warning</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">info</span><span class="dl">"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>用法:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">results</span><span class="p">:</span> <span class="nx">RiskResult</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">乳腺癌低风险</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">请继续保持健康生活方式</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span>
    <span class="p">},</span>
<span class="p">]</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">result</span><span class="p">,</span> <span class="nx">setResult</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">RiskResult</span><span class="o">&gt;</span><span class="p">()</span>
</code></pre></div></div>


        
      </section>

      <footer class="page__meta">
        
        


        

  <p class="page__date"><strong><i class="fas fa-fw fa-calendar-alt" aria-hidden="true"></i> 更新时间:</strong> <time class="dt-published" datetime="2023-05-18T00:00:00+08:00">May 18, 2023</time></p>

      </footer>

      

      
  <nav class="pagination">
    
      <a href="/programming/2022/10/14/difference-between-gulp-and-webpack.html" class="pagination--pager" title="Gulp 和 Webpack 有什么区别?
">上一页</a>
    
    
      <a href="/tools/2023/05/27/jekyll-theme-minimal-mistakes.html" class="pagination--pager" title="Jekyll 主题 Minimal Mistakes 使用总结
">下一页</a>
    
  </nav>

    </div>

    
      <div class="page__comments">
  
  
      <h4 class="page__comments-title">留下评论</h4>
      <section id="disqus_thread"></section>
    
</div>

    
  </article>

  
  
</div>

</div>

搜索:


  <div class="search-content">
    <div class="search-content__inner-wrap"><form class="search-content__form" onkeydown="return event.key != 'Enter';" role="search">
    <label class="sr-only" for="search">
      输入您要搜索的关键词...
    </label>
    <input type="search" id="search" class="search-input" tabindex="-1" placeholder="输入您要搜索的关键词..." />
  </form>
  <div id="results" class="results"></div></div>

  </div>

footer:

<div id="footer" class="page__footer">
  <footer>
    <!-- start custom footer snippets -->

<!-- end custom footer snippets -->
    <div class="page__footer-follow">
  <ul class="social-icons">
    
      <li><strong>关注:</strong></li>
    

    
      
        
          <li><a href="https://github.com/feelang" rel="nofollow noopener noreferrer"><i class="fab fa-fw fa-github" aria-hidden="true"></i> Github</a></li>
        
      
        
          <li><a href="https://weibo.com/u/1670598115" rel="nofollow noopener noreferrer"><i class="fab fa-fw fa-weibo" aria-hidden="true"></i> 微博</a></li>
        
      
    

    
      <li><a href="/feed.xml"><i class="fas fa-fw fa-rss-square" aria-hidden="true"></i> Feed</a></li>
    
  </ul>
</div>

<div class="page__footer-copyright">&copy; 2024 <a href="https://feelang.xyz">feelang的技术博客</a>. 技术来自于 <a href="https://jekyllrb.com" rel="nofollow">Jekyll</a> &amp; <a href="https://mademistakes.com/work/minimal-mistakes-jekyll-theme/" rel="nofollow">Minimal Mistakes</a>.</div>

  </footer>
</div>

最后是 scripts:


  <script src="/assets/js/main.min.js"></script>




<script src="/assets/js/lunr/lunr.min.js"></script>
<script src="/assets/js/lunr/lunr-store.js"></script>
<script src="/assets/js/lunr/lunr-en.js"></script>




  <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-EMB5R38212"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-EMB5R38212', { 'anonymize_ip': false});
</script>






    
  <script>
    var disqus_config = function () {
      this.page.url = "https://feelang.xyz/tools/2023/05/27/jekyll-theme-minimal-mistakes.html";  /* Replace PAGE_URL with your page's canonical URL variable */
      this.page.identifier = "/tools/2023/05/27/jekyll-theme-minimal-mistakes"; /* Replace PAGE_IDENTIFIER with your page's unique identifier variable */
    };
    (function() { /* DON'T EDIT BELOW THIS LINE */
      var d = document, s = d.createElement('script');
      s.src = 'https://feelang-github-io.disqus.com/embed.js';
      s.setAttribute('data-timestamp', +new Date());
      (d.head || d.body).appendChild(s);
    })();
  </script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>


  




_includes/scripts.html 中包含了以下内容:

1)自定义 js,默认使用 /assets/js/main.min.js


  <script src="/assets/js/main.min.js"></script>

2)搜索



<script src="/assets/js/lunr/lunr.min.js"></script>
<script src="/assets/js/lunr/lunr-store.js"></script>
<script src="/assets/js/lunr/lunr-en.js"></script>

3)埋点




  <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-EMB5R38212"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-EMB5R38212', { 'anonymize_ip': false});
</script>




4)评论



    
  <script>
    var disqus_config = function () {
      this.page.url = "https://feelang.xyz/tools/2023/05/27/jekyll-theme-minimal-mistakes.html";  /* Replace PAGE_URL with your page's canonical URL variable */
      this.page.identifier = "/tools/2023/05/27/jekyll-theme-minimal-mistakes"; /* Replace PAGE_IDENTIFIER with your page's unique identifier variable */
    };
    (function() { /* DON'T EDIT BELOW THIS LINE */
      var d = document, s = d.createElement('script');
      s.src = 'https://feelang-github-io.disqus.com/embed.js';
      s.setAttribute('data-timestamp', +new Date());
      (d.head || d.body).appendChild(s);
    })();
  </script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>


  

5)js 脚本


最后总结一下 _layouts/default.html 的代码结构:

  • html
    • head
      • include head.html
        • include seo.html
        • site.atom_feed
        • viewport
        • /assets/css/main.css
        • fontawesome
        • site.head_scripts
      • include head/custom.html
    • body
      • include_cached skip-links.html
        • #site-nav
        • #main
        • #footer
      • include_cached masthead.html
        • site.logo
        • site.masthead site.title
        • site.subtitle
        • site.data.navigation.main
        • site.search
        • Toggle menu
      • `

好长一段时间没写前端,很多知识点都忘掉了,为了防止遗忘,趁着项目刚结束,赶紧总结一下。

邂逅 Next.js

这个项目用的是 next.js,也是 react 官方推荐的。今天才知道原来 react 已经升级改版,我之前掌握的技能早已过时。

第一次用 next.js,不了解技术细节,不过从使用感受来说,还不错,用起来很方便。

从官网简介可以看出,这是一个全栈框架:

Next.js enables you to create full-stack Web applications by extending the latest React features, and integrating powerful Rust-based JavaScript tooling for the fastest builds.

根据已经掌握的 antd 知识,很快就把第一个页面搞定了。

开始搞第二个页面时,我开始网上寻找 Router 解决方案,找到了 React Router

发现这个框架也已经升级改版,而我只掌握了上一个版本的用法,所以就开始翻文档,学习新用法,但是内容实在太长了,读不下去。

顺便发现了 next.js 自身内置了路由模块——API Routes

继续翻文档,结果云里雾里,根本看不懂。

灵机一动,不如去看看视频教程,找到了 Next.js App Router: Routing, Data Fetching, Caching

原来在 next.js 项目中,只需要在 app 目录下新建一个文件夹就可以自动生成路由,实在方便。

就这样新建了第二个页面。

然后开始使用 antdForm 组件写页面。

老朋友 AntD

使用 Form

<Form
    form={form}
    layout="vertical"
    onFinish={onFinish}
    onFinishFailed={onFinishFailed}
>

其中:

  • form={form}form 来自:const [form] = Form.useForm()
  • onFinishonFinishFailed 是两个函数
 const onFinish = (values: any) => {
    console.log(values)
 }

 const onFinishFailed = (errorInfo: any) => {
    console.log(errorInfo)
 }

页面底部添加两个按钮:计算 & 重置。

<Form.Item>
    <Space>
        <Button type="primary" htmlType="submit">计算</Button>
        <Button htmlType="reset">重置</Button>
    </Space>
</Form.Item>

其中 <Space> 一用,就不用单独设置边距了,非常方便。

这两个按钮的功能通过设置 htmlType 属性来实现,其他任何代码不需要。

Form.Item 的联动校验

其中一个输入框要填写年龄,且是必填:

<Form.Item label="1 年龄(age)" name="age"
    rules={[{ required: true, message: '请输入年龄' }]}
>
    <InputNumber placeholder="请输入年龄" min={18} max={100}
        onChange={setAge}
    />
</Form.Item>

利用 setAge 接收输入值:

const [age, setAge] = useState<string | number | null>(null)

需要留意的是,InputNumberonChange 属性的变量类型为 string | number | null

第二个输入框也是年龄,但它的输入值必须小于 age

<Form.Item label="3.1 绝经年龄(age at menopause)"
           name="menopauseAge"
           dependencies={['age']}
           rules={[
               {
                   required: true,
                   message: '请输入年龄',
               },
               ({ getFieldValue }) => ({
                   validator(_, value) {
                       if (!value || getFieldValue('age') >= value) {
                           return Promise.resolve();
                       }
                       return Promise.reject(new Error(`绝经年龄不得大于当前年龄(${age})`));
                   },
               }),
           ]}
>

这个用法值得仔细记录一下。

先看 dependencies 的用法,官网介绍如下,简单明了。

当字段间存在依赖关系时使用。如果一个字段设置了 dependencies 属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。 一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 dependencies 后,“密码”字段更新会重新触发“校验密码”的校验逻辑。

再看 rules 用法:

校验规则,设置字段的校验逻辑。

类型为 Rule[],是个数组。

Rule 定义如下:

type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);

可以看出,Rule 支持两种类型,所以上面代码中 rules 的第一个元素类型为 RuleConfig

{
    required: true,
    message: '请输入年龄',
},

第二个元素类型为 ((form: FormInstance) => RuleConfig),是个高阶函数。

({ getFieldValue }) => ({
    validator(_, value) {
        if (!value || getFieldValue('age') >= value) {
            return Promise.resolve();
        }
        return Promise.reject(new Error(`绝经年龄不得大于当前年龄(${age})`));
    },
}),

由此可以推测出 getFieldValueFormInstance 的一个属性,通过看源码可以证实:

export interface FormInstance<Values = any> {
    getFieldValue: (name: NamePath) => StoreValue;
    getFieldsValue: (() => Values) & ((nameList: NamePath[] | true, filterFunc?: (meta: Meta) => boolean) => any);
    getFieldError: (name: NamePath) => string[];
    getFieldsError: (nameList?: NamePath[]) => FieldError[];
    getFieldWarning: (name: NamePath) => string[];
    isFieldsTouched: ((nameList?: NamePath[], allFieldsTouched?: boolean) => boolean) & ((allFieldsTouched?: boolean) => boolean);
    isFieldTouched: (name: NamePath) => boolean;
    isFieldValidating: (name: NamePath) => boolean;
    isFieldsValidating: (nameList: NamePath[]) => boolean;
    resetFields: (fields?: NamePath[]) => void;
    setFields: (fields: FieldData[]) => void;
    setFieldValue: (name: NamePath, value: any) => void;
    setFieldsValue: (values: RecursivePartial<Values>) => void;
    validateFields: ValidateFields<Values>;
    submit: () => void;
}

validator 也是一个高阶函数:

type Validator = (rule: RuleObject, value: StoreValue, callback: (error?: string) => void) => Promise<void | any> | void;

返回值是一个 Promisevoid

通过以上分析,可以总结出:当两个 Form.Item 之间存在依赖时,可通过 dependenciesrules 属性设定依赖逻辑。

TypeScript tips

string => number

// menopauseAge 是个 `string`
const menopauseAge = values.menopauseAge ? +values.menopauseAge : 0

interface

interface RiskResult {
    message: string,
    description: string,
    type: "success" | "warning" | "error" | "info"
}

用法:

const results: RiskResult[] = [
    {
        message: '乳腺癌低风险',
        description: '请继续保持健康生活方式',
        type: 'success'
    },
]
const [result, setResult] = useState<RiskResult>()

更新时间:

留下评论

` - include_cached search/search_form.html - lunr - google - algolia - footer - include footer/custom.html - site.data.ui-text[site.locale].follow_label - site.footer.links - site.atom_feed - copyright - include_cached footer.html - include scripts.html - site.footer_scripts - site.search - include analytics.html - google - google-universal - google-gtag - custom - include /comments-providers/scripts.html - diques - discourse - facebook - staticman - staticman_v2 - utterances - giscus - custom - site.after_footer_scripts

更新时间:

留下评论