亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

「每日一瞥

XboxYan / 1959人閱讀

摘要:首先,我們需要一個(gè)基本框架來處理表單域變化和表格提交。最起碼我們需要提供一個(gè)來告訴如果用戶還沒有對表單域進(jìn)行改動(dòng),就不必展示錯(cuò)誤。我們需要一個(gè)來標(biāo)識(shí)用戶已嘗試提交表單,還需要來標(biāo)識(shí)表單是否正在提交以及每個(gè)表單域是否正在進(jìn)行異步校驗(yàn)。

用 React Hooks 寫異步表單校驗(yàn)

React Hooks vs HOC 性能對比探討

Flexbox vs Grid

git-history 把玩

一個(gè) React 優(yōu)化模式

用 React Hooks 寫異步表單校驗(yàn)

春節(jié)期間,React 發(fā)布了 16.8 的版本,正式支持了 React Hooks。本文將使用 React Hooks API 通過 100 行代碼來實(shí)現(xiàn)支持異步的表單校驗(yàn)功能。當(dāng)然,本文最終的例子接近 200 行代碼,這其中包含了使用 Hooks 的部分。

許多與表單相關(guān)的教程都不怎么涉及這三個(gè)問題:

異步校驗(yàn)

其他字段發(fā)生變化時(shí)觸發(fā)的字段校驗(yàn)

校驗(yàn)頻率的優(yōu)化

在實(shí)際的表單應(yīng)用場景中,上述三點(diǎn)是非常普遍的。那么這里我們就以下面的若干點(diǎn)作為實(shí)現(xiàn)的目標(biāo):

同步校驗(yàn)表單域及當(dāng)表單域變化時(shí)的依賴域

異步校驗(yàn)表單域及當(dāng)表單域變化時(shí)的依賴域

提交前所有表單域的同步校驗(yàn)

提交前所有表單域的異步步校驗(yàn)

嘗試異步提交,如失敗,展示返回值中的錯(cuò)誤信息

為開發(fā)者暴露校驗(yàn)方法,開發(fā)者可在 onBlur 或其他時(shí)機(jī)進(jìn)行校驗(yàn)

允許一個(gè)域的多種校驗(yàn)

如果表單有錯(cuò)誤,攔截提交

直到表單修改后,或嘗試發(fā)起表單提交后才展示一個(gè)表單域的錯(cuò)誤信息

下面的例子是一個(gè)帶有用戶名(username),密碼(password)和確認(rèn)密碼(confirmPassword)的賬號(hào)注冊場景。我們先從簡單的開始:

import React, { Component, useState, useEffect, useRef } from "react";
import { useForm, useField } from "./formHooks";

const form = useForm({
  onSubmit,
});

const usernameField = useField("username", form, {
  defaultValue: "",
  validations: [
    async formData => {
      await timeout(2000);
      return formData.username.length < 6 && "Username already exists";
    }
  ],
  fieldsToValidateOnChange: []
});
const passwordField = useField("password", form, {
  defaultValue: "",
  validations: [
    formData =>
      formData.password.length < 6 && "Password must be at least 6 characters"
  ],
  fieldsToValidateOnChange: ["password", "confirmPassword"]
});
const confirmPasswordField = useField("confirmPassword", form, {
  defaultValue: "",
  validations: [
    formData =>
      formData.password !== formData.confirmPassword &&
      "Passwords do not match"
  ],
  fieldsToValidateOnChange: ["password", "confirmPassword"]
});

// const { onSubmit, getFormData, addField, isValid, validateFields, submitted, submitting } = form
// const { name, value, onChange, errors, setErrors, pristine, validate, validating } = usernameField

這是相對簡單的 API,但應(yīng)該會(huì)給我們很大的靈活性。可以注意到,此接口包含兩個(gè)命名類似的函數(shù),validation 和 validate。我們將定義 validation 為接收表單數(shù)據(jù)和字段名稱的函數(shù),如果發(fā)現(xiàn)問題則返回錯(cuò)誤消息,否則將返回 false。另一方面,函數(shù) validate 將運(yùn)行字段的所有校驗(yàn)函數(shù),并將更新字段的錯(cuò)誤列表。

首先,我們需要一個(gè)基本框架來處理表單域變化和表格提交。我們第一次迭代不包括任何校驗(yàn),它只會(huì)處理表單狀態(tài)。

// Skipping some boilerplate: imports, ReactDOM, etc.

export const useField = (name, form, { defaultValue } = {}) => {
  let [value, setValue] = useState(defaultValue);

  let field = {
    name,
    value,
    onChange: e => {
      setValue(e.target.value);
    }
  };
  // Register field with the form
  form.addField(field);
  return field;
};

export const useForm = ({ onSubmit }) => {
  let fields = [];

  const getFormData = () => {
    // Get an object containing raw form data
    return fields.reduce((formData, field) => {
      formData[field.name] = field.value;
      return formData;
    }, {});
  };

  return {
    onSubmit: async e => {
      e.preventDefault(); // Prevent default form submission
      return onSubmit(getFormData());
    },
    addField: field => fields.push(field),
    getFormData
  };
};

const Field = ({ label, name, value, onChange, ...other }) => {
  return (
    
      {label}
      
    
  );
};

const App = props => {
  const form = useForm({
    onSubmit: async formData => {
      window.alert("Account created!");
    }
  });

  const usernameField = useField("username", form, {
    defaultValue: ""
  });
  const passwordField = useField("password", form, {
    defaultValue: ""
  });
  const confirmPasswordField = useField("confirmPassword", form, {
    defaultValue: ""
  });

  return (
    
); };

我們只對字段值進(jìn)行跟蹤。每個(gè)字段在使用 useField 初始化結(jié)束時(shí)使用 form 進(jìn)行注冊。我們的 onChange 也很簡單。而 getFormData 也就是遍歷表單字段,生成一個(gè)表單鍵值對進(jìn)行存儲(chǔ)。

現(xiàn)在讓我們添加對校驗(yàn)的支持。我們還不會(huì)指定在字段值更改時(shí)應(yīng)校驗(yàn)?zāi)男┳侄巍O喾?,我們將在值發(fā)生更改時(shí)以及每次提交表單時(shí)校驗(yàn)所有字段。

export const useField = (
  name,
  form,
  { defaultValue, validations = [] } = {}
) => {
  let [value, setValue] = useState(defaultValue);
  let [errors, setErrors] = useState([]);

  const validate = async () => {
    let formData = form.getFormData();
    let errorMessages = await Promise.all(
      validations.map(validation => validation(formData, name))
    );
    errorMessages = errorMessages.filter(errorMsg => !!errorMsg);
    setErrors(errorMessages);
    let fieldValid = errorMessages.length === 0;
    return fieldValid;
  };

  useEffect(
    () => {
      form.validateFields(); // Validate fields when value changes
    },
    [value]
  );

  let field = {
    name,
    value,
    errors,
    validate,
    setErrors,
    onChange: e => {
      setValue(e.target.value);
    }
  };
  // Register field with the form
  form.addField(field);
  return field;
};

export const useForm = ({ onSubmit }) => {
  let fields = [];

  const getFormData = () => {
    // Get an object containing raw form data
    return fields.reduce((formData, field) => {
      formData[field.name] = field.value;
      return formData;
    }, {});
  };

  const validateFields = async () => {
    let fieldsToValidate = fields;
    let fieldsValid = await Promise.all(
      fieldsToValidate.map(field => field.validate())
    );
    let formValid = fieldsValid.every(isValid => isValid === true);
    return formValid;
  };

  return {
    onSubmit: async e => {
      e.preventDefault(); // Prevent default form submission
      let formValid = await validateFields();
      return onSubmit(getFormData(), formValid);
    },
    addField: field => fields.push(field),
    getFormData,
    validateFields
  };
};

const Field = ({
  label,
  name,
  value,
  onChange,
  errors,
  setErrors,
  validate,
  ...other
}) => {
  let showErrors = !!errors.length;
  return (
    
      {label}
      
      
        {showErrors &&
          errors.map(errorMsg => 
{errorMsg}
)}
); }; const App = props => { const form = useForm({ onSubmit: async formData => { window.alert("Account created!"); } }); const usernameField = useField("username", form, { defaultValue: "", validations: [ async formData => { await timeout(2000); return formData.username.length < 6 && "Username already exists"; } ] }); const passwordField = useField("password", form, { defaultValue: "", validations: [ formData => formData.password.length < 6 && "Password must be at least 6 characters" ] }); const confirmPasswordField = useField("confirmPassword", form, { defaultValue: "", validations: [ formData => formData.password !== formData.confirmPassword && "Passwords do not match" ] }); return (
); };

上面的代碼是一個(gè)改進(jìn),乍一看,它似乎可以很好地工作,但實(shí)際上它還差很多。我們需要一些必要的 flag 來避免在不適當(dāng)?shù)臅r(shí)機(jī)出現(xiàn)錯(cuò)誤信息,在用戶再次修改之前能夠立刻顯示出相應(yīng)的錯(cuò)誤。

最起碼我們需要提供一個(gè) pristine flag 來告訴 UI 如果用戶還沒有對表單域進(jìn)行改動(dòng),就不必展示錯(cuò)誤。當(dāng)然我們還可以更進(jìn)一步,引入其他更多的 flag 來細(xì)化行為。

我們需要一個(gè) flag 來標(biāo)識(shí)用戶已嘗試提交表單,還需要 flag 來標(biāo)識(shí)表單是否正在提交以及每個(gè)表單域是否正在進(jìn)行異步校驗(yàn)。

此外,為什么我們要在 useEffect中 調(diào)用 validateFields 而不是 onChange 中?我們需要 useEffect,因?yàn)?setValue 是異步發(fā)生的,而這兩者都既不返回 promise 也不提供回調(diào)。因此,我們可以確定 setValue 已經(jīng)完成的唯一方法是通過 useEffect 監(jiān)聽 value 的變化。

下面我們就來嘗試實(shí)現(xiàn)這些 flag 并借助他們來清理 UI 及處理邊界 case,注釋已經(jīng)詳細(xì)的寫在代碼中:

export const useField = (
  name,
  form,
  { defaultValue, validations = [], fieldsToValidateOnChange = [name] } = {}
) => {
  let [value, setValue] = useState(defaultValue);
  let [errors, setErrors] = useState([]); // 初始時(shí)沒有錯(cuò)誤
  let [pristine, setPristine] = useState(true); // 初始時(shí)表單域未修改
  let [validating, setValidating] = useState(false); // 初始時(shí)未處于校驗(yàn)狀態(tài)
  let validateCounter = useRef(0); // 計(jì)數(shù)器 優(yōu)化多次校驗(yàn)的場景

  const validate = async () => {
    let validateIteration = ++validateCounter.current; // 每次調(diào)用先 +1
    setValidating(true);
    let formData = form.getFormData();
    let errorMessages = await Promise.all(
      validations.map(validation => validation(formData, name))
    ); // 所有的校驗(yàn)遍歷 返回 false 或 錯(cuò)誤提示字符串
    errorMessages = errorMessages.filter(errorMsg => !!errorMsg);
    if (validateIteration === validateCounter.current) {
      // 只會(huì)使用最新的一次校驗(yàn)
      setErrors(errorMessages);
      setValidating(false); // 校驗(yàn)狀態(tài)置回
    }
    let fieldValid = errorMessages.length === 0;
    return fieldValid; // 表單域正確 flag
  };

  useEffect(
    () => {
      if (pristine) return; // 避免剛掛載就開始校驗(yàn)
      form.validateFields(fieldsToValidateOnChange); // 注意修改狀態(tài)的位置
    },
    [value]
  );

  let field = {
    name,
    value,
    errors,
    setErrors,
    pristine,
    onChange: e => {
      if (pristine) {
        setPristine(false); // 只在第一次修改時(shí)會(huì)觸發(fā)
      }
      setValue(e.target.value); // 這里只修改表單 不修改 Hooks 的狀態(tài)!
    },
    validate,
    validating
  };
  form.addField(field); // 注冊表單域
  return field;
};

export const useForm = ({ onSubmit }) => {
  let [submitted, setSubmitted] = useState(false); // 已經(jīng)提交
  let [submitting, setSubmitting] = useState(false); // 正在提交
  let fields = [];

  // 參數(shù)就是 fieldsToValidateOnChange,變動(dòng)時(shí)需要校驗(yàn)的表單域
  const validateFields = async fieldNames => {
    let fieldsToValidate; // 存放 useField 生成的需要校驗(yàn)的表單域
    if (fieldNames instanceof Array) {
      fieldsToValidate = fields.filter(field =>
        fieldNames.includes(field.name)
      );
    } else {
      //if fieldNames not provided, validate all fields
      fieldsToValidate = fields;
    }
    let fieldsValid = await Promise.all(
      fieldsToValidate.map(field => field.validate())
    );
    let formValid = fieldsValid.every(isValid => isValid === true);
    return formValid;
  };

  const getFormData = () => {
    return fields.reduce((formData, f) => {
      formData[f.name] = f.value;
      return formData;
    }, {});
  };

  return {
    onSubmit: async e => {
      e.preventDefault();
      setSubmitting(true);
      setSubmitted(true); // User has attempted to submit form at least once
      let formValid = await validateFields(); // 全部校驗(yàn)
      let returnVal = await onSubmit(getFormData(), formValid);
      setSubmitting(false);
      return returnVal;
    },
    isValid: () => fields.every(f => f.errors.length === 0), // 全部域沒錯(cuò)誤
    addField: field => fields.push(field), // 注冊表單域
    getFormData,
    validateFields,
    submitted,
    submitting
  };
};

// 表單域
const Field = ({
  label,
  name,
  value,
  onChange,
  errors,
  setErrors,
  pristine,
  validating,
  validate,
  formSubmitted,
  ...other
}) => {
  // 如果 修改過或提交過 且存在校驗(yàn)錯(cuò)誤,展示錯(cuò)誤
  let showErrors = (!pristine || formSubmitted) && !!errors.length;
  return (
    
      {label}
       !pristine && validate()} // 如果已經(jīng)修改過 則觸發(fā)校驗(yàn)
        endAdornment={
          
            {
                  // 通過 validating flag 控制異步校驗(yàn)的旋轉(zhuǎn) icon
                  validating && 
            }
          
        }
        {...other}
      />
      
        {showErrors &&
          errors.map(errorMsg => 
{errorMsg}
)}
); }; const App = props => { const form = useForm({ onSubmit: async (formData, valid) => { if (!valid) return; await timeout(2000); // Simulate network time if (formData.username.length < 10) { //Simulate 400 response from server usernameField.setErrors(["Make a longer username"]); } else { //Simulate 201 response from server window.alert( `form valid: ${valid}, form data: ${JSON.stringify(formData)}` ); } } }); const usernameField = useField("username", form, { defaultValue: "", validations: [ async formData => { await timeout(2000); return formData.username.length < 6 && "Username already exists"; } ], fieldsToValidateOnChange: [] }); const passwordField = useField("password", form, { defaultValue: "", validations: [ formData => formData.password.length < 6 && "Password must be at least 6 characters" ], fieldsToValidateOnChange: ["password", "confirmPassword"] }); const confirmPasswordField = useField("confirmPassword", form, { defaultValue: "", validations: [ formData => formData.password !== formData.confirmPassword && "Passwords do not match" ], fieldsToValidateOnChange: ["password", "confirmPassword"] }); let requiredFields = [usernameField, passwordField, confirmPasswordField]; return (
); };

這一版我們加了很多內(nèi)容,首先包括 4 個(gè) flag:

pristine

validating

submitted

submitting

當(dāng)然,還增加了 fieldsToValidateOnChange 這一參數(shù),傳入 validateFields 來表示當(dāng)表單域變化時(shí)哪些域要觸發(fā)校驗(yàn)。

這些 flag 的用處就是:控制 UI 中的小菊花和錯(cuò)誤提示,以及在適當(dāng)時(shí)刻禁用提交按鈕。

需要注意的是 validateCounter。我們需要跟蹤調(diào)用驗(yàn)證函數(shù)的次數(shù),因?yàn)樵谛r?yàn)函數(shù)完成時(shí),可能會(huì)再次調(diào)用新的校驗(yàn)。如果是這種情況,我們需要忽略此次調(diào)用的結(jié)果,并僅使用最近調(diào)用的結(jié)果來更新表單域的錯(cuò)誤狀態(tài)。

這個(gè)版本還有很多不足,不過這個(gè) Demo 本身還是值得學(xué)習(xí)的。

源地址:https://medium.freecodecamp.o...

React Hooks vs HOC 性能對比探討

Medium 上一篇文章給出結(jié)論說 Hooks 性能相對差一些,Dan Abramov 對此予以回應(yīng)。我們來看看具體是怎么討論的。

為什么說 React Hooks 性能稍遜?

這篇文章中的結(jié)論是,盡管最近 HOC 因?yàn)?wrapper hell 而越來越多的被詬病,根據(jù)其評(píng)判標(biāo)準(zhǔn),HOC 仍然比 Hooks 快。當(dāng)然他也提出,如果評(píng)判標(biāo)準(zhǔn)的測試結(jié)果有誤,也請隨時(shí)指正。

那么在了解 Dan 的回應(yīng)之前,我們先來看看他是如何對二者進(jìn)行對比的。

測試 App

作者設(shè)計(jì)了一個(gè)簡單的測試場景:簡而言之,就是使用 React Hooks 和 HOC 分別渲染一個(gè)具有 10,000 個(gè)子組件的根組件。其中,每個(gè)子組件包含三個(gè)狀態(tài)值,以及第一次 render 后設(shè)置三個(gè)狀態(tài)值的副作用。根組件負(fù)責(zé)記錄渲染完這 10,000 個(gè)子組件所耗費(fèi)的時(shí)間,當(dāng)然,這個(gè)記錄過程也是通過一個(gè)副作用來完成。

Hooks 版本
import React, { useEffect, useState } from "react";
import { render } from "react-dom";
const array = [];
for (let i = 0; i < 10000; i++) array[i] = true;
const Component = () => {
  const [a, setA] = useState("");
  const [b, setB] = useState("");
  const [c, setC] = useState("");
  
  useEffect(() => {
    setA("A");
    setB("B");
    setC("C");
  }, []);
  return 
{a + b + c}
; }; const Benchmark = ({ start }) => { useEffect(() => { console.log(Date.now() - start); }); return array.map((item, index) => ); }; render(, document.getElementById("root"));
HOC 版本
import React from "react";
import { render } from "react-dom";
import { compose, withState, withEffect } from "@reactorlib/core";
const array = [];
for (let i = 0; i < 10000; i++) array[i] = true;
const _Component = ({ a, b, c }) => {
  return 
{a + b + c}
; }; const Component = compose( withState({ a: "", b: "", c: "" }), withEffect(({ setA, setB, setC }) => { setA("A"); setB("B"); setC("C"); }, true) )(_Component); const _Benchmark = () => { return array.map((item, index) => ); }; const Benchmark = compose( withEffect(({ start }) => { console.log(Date.now() - start); }) )(_Benchmark); render(, document.getElementById("root"));

上述的 reactorlib 是作者自己封的包,基本上代碼沒什么差別。

測試設(shè)備

具體設(shè)備我們并不關(guān)心,兩者都是在 15 年的 MacBook 上跑的。

測試結(jié)果

作者展示了 10 組結(jié)果,從數(shù)據(jù)上看,似乎是 Hooks 穩(wěn)定的落后于 HOC。

Rendering Time in milliseconds
Run#     Hooks        HOCs
-----------------------------
 1       2197         1440
 2       2302         1757
 3       2749         1407
 4       2243         1309
 5       2167         1644
 6       2219         1516
 7       2322         1673
 8       2268         1630
 9       2164         1446
10       2071         1597

那么事實(shí)真的如此嗎?

評(píng)判標(biāo)準(zhǔn)存在什么問題?

Dan 提出,首先應(yīng)該統(tǒng)一在生產(chǎn)模式才有比較的意義,畢竟開發(fā)模式下會(huì)有大量的提示信息等拖慢速度,生產(chǎn)模式的性能才是現(xiàn)實(shí)中更需要考量的。

當(dāng)統(tǒng)一成生產(chǎn)模式后,二者跑出的結(jié)果都顯著變快,不過 Hooks 仍略慢一籌。

Hooks     HOCs
(NOTE: these results are still wrong, see below)
175       156
171       164
169       154
181       138
167       159
153       194
151       152
155       152
147       163
160       162

當(dāng)然結(jié)果還不對,原因在于這兩者并沒有在相同的評(píng)判標(biāo)準(zhǔn)下運(yùn)行。

我們看到,Hooks 版本的測試是通過 useEffect() 來更新狀態(tài)和計(jì)算時(shí)間的。我們看下文檔中的說法:

與 componentDidMount 和 componentDidUpdate 不同,傳遞到 useEffect 的函數(shù)執(zhí)行的時(shí)間是晚于布局和繪制的。這就使其非常適合一些常見的副作用:設(shè)置訂閱和事件等,因?yàn)榇蠖鄶?shù)工作都不應(yīng)阻塞瀏覽器更新視圖。

因此,useEffect() 可以讓瀏覽器在運(yùn)行副作用之前就完成視圖的繪制,對于用戶來說,useEffect() 通常能提供更好的體驗(yàn),因?yàn)?strong>初始渲染不需要等待副作用的執(zhí)行,而這一點(diǎn)正是 React class 存在的一個(gè)普遍問題。

因?yàn)樾枰却跏间秩荆?useEffect() 版的結(jié)果自然會(huì)比 class 版本的要慢一些了。那么我們來看看 HOC 到底做了什么呢?

從源碼上看,HOC 是在 componentDidMount() 中設(shè)置狀態(tài)并 log 時(shí)間開銷。這就意味著這一過程執(zhí)行的更早了一些,但同時(shí)用戶還沒有真實(shí)看到視圖。即使按照擬定的標(biāo)準(zhǔn)來評(píng)判,結(jié)果上更好看,但 app 本身的響應(yīng)性并不強(qiáng)。

為了將二者放到統(tǒng)一評(píng)判標(biāo)準(zhǔn)上進(jìn)行比較,Dan 使用了 useLayoutEffect(),這一副作用是在布局階段執(zhí)行的,這與 componentDidMount() 相似。重新跑一邊程序就會(huì)發(fā)現(xiàn),Hooks 明顯是要快了一些。

Hooks   HOCs
(These rows compare the same thing--although not a very useful one)
121     156
106     170
111     157
141     152
111     156
121     158
105     170
111     162
108     166
108     157

那會(huì)不會(huì)是因?yàn)?useLayoutEffect() 本身就比 useEffect() 執(zhí)行的快嗎?并不,這只是意味著 useLayoutEffect() 或 componentDidMount() 執(zhí)行的更早一些。除非我們的副作用場景與布局有關(guān),否則我們通常都會(huì)希望先渲染出初始視圖,然后在執(zhí)行副作用。而這正式 useEffect() 幫助我們做到的。

如果我們在 useEffect() 中更新狀態(tài),但在 useLayoutEffect()(類似 componentDidMount)中計(jì)算時(shí)間,我們甚至能得到更好的結(jié)果。

Hooks   HOCs
(Don"t pass a screenshot like this around--read the text below.
 They"re not doing equivalent work!)
106     174
104     198
88      149
88      154
88      149
89      163
85      162
90      157
89      164
91      153

這個(gè)結(jié)果并不奇怪,因?yàn)?useEffect() 版本在渲染之前所做的工作一定是更少的:我們首先展示了初始渲染的結(jié)果,然后執(zhí)行了改變狀態(tài)的副作用,這就意味著更新的時(shí)候會(huì)有一些閃爍。不過,這一評(píng)判標(biāo)準(zhǔn)其實(shí)并不能代表真實(shí)的 UI 場景,我們并不能斷定究竟哪種方法更好。Hooks 可以讓我們選擇是否阻塞渲染過程,從而根據(jù)自己的場景來決定什么是比較好的。

當(dāng)然,渲染 10,000 個(gè)文字節(jié)點(diǎn)并且一次性更新它們并不能真實(shí)的還原現(xiàn)實(shí)應(yīng)用中所會(huì)遇到的性能挑戰(zhàn),同時(shí),這一場景也不能為不同場景下的性能權(quán)衡提供充分的上下文。

總之,這二者的對比本身并沒那么重要,當(dāng)發(fā)布一項(xiàng)評(píng)判標(biāo)準(zhǔn)時(shí),知道瀏覽器中究竟發(fā)生了什么才更值得深究。

Benchmarking is hard.

源地址:
https://hackernoon.com/react-...
https://medium.com/@dan_abram...

Flexbox vs Grid

Flexbox 和 grid 之間有很多相似的地方,它們可以拉伸,可以收縮,可以居中,可以重新排序,可以排列等等。很多布局場景下,這兩者我們都可以選來用,當(dāng)然,有些場景也確實(shí)存在其中一個(gè)比另一個(gè)更合適的情況。那么我們就來關(guān)注下這二者的差異究竟在哪里:

Flexbox 可以選擇性的 wrap

我們可以指定 Flexbox 在一行放不下后另一起行,當(dāng)然,這樣就會(huì)產(chǎn)生參差不齊的感覺;而 Grid 則會(huì)充滿一行(如果我們允許自動(dòng)充滿的話)。

Flexbox on top, Grid on bottom
Flexbox 可看作「一維」,Grid 可看作「二維」

盡管 Flexbox 可以形成行列布局,并且允許元素 wrap,但我們無法指定一個(gè)元素作為一行的結(jié)束,而僅僅是沿著一個(gè)坐標(biāo)軸推出去,然后按照能否 wrap 挨個(gè)排列而已。

.parent {
  display: flex;
  flex-flow: row wrap; /* 元素會(huì)沿著一行盡可能的排列 然后根據(jù)情況進(jìn)行 wrap */
}

而 Grid 就更像是「二維」的東西,我們可以指定行和列的尺寸,顯式的指定東西在行列的位置:

.parent {
  display: grid;
  grid-template-columns: 3fr 1fr; /* Two columns, one three times as wide as the other */
  grid-template-rows: 200px auto 100px; /* Three columns, two with explicit widths */
  grid-template-areas:
    "header header header"
    "main . sidebar"
    "footer footer footer";
}

/*
  Now, we can explicitly place items in the defined rows and columns.
*/
.child-1 {
  grid-area: header;
}

.child-2 {
  grid-area: main;
}

.child-3 {
  grid-area: sidebar;
}

.child-4 {
  grid-area: footer;
}

Flexbox on top, Grid on bottom
Grid 的定位基本在父元素上,而 Flexbox 基本在子元素上
/*
  Flex 子元素完成大部分定位工作
*/
.flexbox {
  display: flex;
  > div {
    &:nth-child(1) { // logo
      flex: 0 0 100px;
    }
    &:nth-child(2) { // search
      flex: 1;
      max-width: 500px;
    }
    &:nth-child(3) { // avatar
      flex: 0 0 50px;
      margin-left: auto;
    }
  }
}

/*
  Grid 父元素完成大部分定位工作
*/
.grid {
  display: grid;
  grid-template-columns: 1fr auto minmax(100px, 1fr) 1fr;
  grid-template-rows: 100px repeat(3, auto) 100px;
  grid-gap: 10px;
}
Grid 更擅長覆蓋

如果是 Flexbox 想做到元素重疊的話,就要尋求一些傳統(tǒng)方法,如負(fù) margin、transform 或絕對定位。當(dāng)時(shí)通過 Grid 我們可以講子元素放置的覆蓋網(wǎng)格線,甚至將元素完全覆蓋在同一個(gè)網(wǎng)格里。

Flexbox on top, Grid on bottom
Flexbox 可以把東西「推」走

Flexbox 有個(gè)獨(dú)有的特性,就是可以通過 margin auto 的方式來把子元素「推到一邊」。這主要是因?yàn)?Flexbox 在 margin auto 這塊的一些有趣特性,具體略,下面附一張圖來展示這一特性:

源地址:https://css-tricks.com/quick-...

git-history 把玩

git-history 可以快速的翻看 Github 倉庫中的文件、或者本地倉庫中的文件的提交歷史信息。是不是真的有用另說,效果還是蠻酷的,展示如下(動(dòng)圖約 8M,略過也可):

具體用法在文檔中有寫,就不啰嗦了。

他們也提供了 Chrome 插件,其實(shí)就是個(gè)帶鏈接的按鈕:

那么我們就看看它具體是怎么做的吧(雖然大佬們一眼就知道這是怎么做的了)。

本地版 CLI 解析 邏輯

毫無疑問的是,這個(gè)小應(yīng)用的核心之一就是 git 命令,那么我們先來看看本地版都做了哪些工作:

首先從入口文件看起,然后會(huì)發(fā)現(xiàn)如下代碼:

const cli = window._CLI;

export default function App() {
  if (cli) {
    return ;
  }

  const [repo, sha, path] = getUrlParams();

  if (!repo) {
    return ;
  } else {
    return ;
  }
}

這個(gè) _CLI 是哪里來的呢?可以看出,如果存在 cli,就會(huì)走 CliApp,也就是本地版的代碼。那我們先去找下 cli 的源頭看看,可以找到如下代碼:

const server = http.createServer((request, response) => {
    if (request.url === "/") {
      Promise.all([indexPromise, commitsPromise]).then(([index, commits]) => {
        const newIndex = index.replace(
          "",
          ``
        );
        var headers = { "Content-Type": "text/html" };
        response.writeHead(200, headers);
        response.write(newIndex);
        response.end();
      });
    } else {
      return handler(request, response, { public: sitePath });
    }
  });
  // ...
}

而在 index.html 中,可以看到:

    

也就是說,本地跑了一個(gè)服務(wù),并且在獲取到提交信息之后,會(huì)替換掉 index.html 中注入的 window._CLI,用來存放文件路徑和提交記錄,再回到上面的 CliApp 上,就可以拿到相應(yīng)數(shù)據(jù)進(jìn)行頁面的渲染了。

在研究提交記錄究竟是怎么獲取的之前(下一小節(jié)統(tǒng)一聊用到的 git 命令),我們先繼續(xù)看下 CliApp 的邏輯。

function CliApp({ data }) {
  let { commits, path } = data;

  const fileName = path.split("/").pop();
  useDocumentTitle(`Git History - ${fileName}`);

  commits = commits.map(commit => ({ ...commit, date: new Date(commit.date) }));
  const [lang, loading, error] = useLanguageLoader(path);

  if (error) {
    return ;
  }

  if (loading) {
    return ;
  }

  return ;
}

CliApp 居然是一個(gè) Hook!其中使用了 useDocumentTitle 和 useLanguageLoader 這兩個(gè)自定義 Hook。useDocumentTitle 本身就是個(gè)副作用,內(nèi)部將傳入的拼接字符串替換為頁面的 title:

export function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

而 useLanguageLoader 做了什么呢?

export function useLanguageLoader(path) {
  return useLoader(async () => {
    const lang = getLanguage(path);
    await loadLanguage(lang);
    return lang;
  }, [path]);
}

可以看到,本身也是調(diào)用了自定義 Hook useLoader,那么 useLoader 又做了什么呢 -。-?

function useLoader(promiseFactory, deps) {
  const [state, setState] = useState({
    data: null,
    loading: true,
    error: null
  });

  useEffect(() => {
    promiseFactory()
      .then(data => {
        setState({
          data,
          loading: false,
          error: false
        });
      })
      .catch(error => {
        setState({
          loading: false,
          error
        });
      });
  }, deps);

  return [state.data, state.loading, state.error];
}

原來,useLoader 就是會(huì)在 useEffect 中根據(jù)指定的依賴執(zhí)行一個(gè) promise,而這個(gè) Hook 在內(nèi)部維護(hù)這個(gè) promise 的相應(yīng)狀態(tài):data、loading、error,說起來就是封裝了一個(gè)「帶狀態(tài)的異步邏輯」。OK,也就是說我們只要關(guān)心傳入的異步方法就對了。回到上面的 useLanguageLoader 繼續(xù)看:

export function getLanguage(filename) {
  return filenameRegex.find(x => x.regex.test(filename)).lang;
}

filenameRegex 其實(shí)就是模塊內(nèi)維護(hù)的針對不同文件后綴的識(shí)別列表,根據(jù)文件路徑返回該文件的文件類型。下面的我們忽略,即可知道 useLanguageLoader 就是用來返回文件類型的。暈,原來是干這的,罷了罷了。

回到 CliApp,我們可以看出,正常渲染的頁面是 History,那我們進(jìn)去看下:

export default function History({ commits, language }) {
  const codes = commits.map(commit => commit.content);
  const slideLines = getSlides(codes, language);
  return ;
}

function Slides({ commits, slideLines }) {
  const [current, target, setTarget] = useSliderSpring(commits.length - 1);
  const setClampedTarget = newTarget =>
    setTarget(Math.min(commits.length - 0.75, Math.max(-0.25, newTarget)));

  const index = Math.round(current);
  const nextSlide = () => setClampedTarget(Math.round(target + 0.51));
  const prevSlide = () => setClampedTarget(Math.round(target - 0.51));
  useEffect(() => {
    document.body.onkeydown = function(e) {
      if (e.keyCode === 39) {
        nextSlide();
      } else if (e.keyCode === 37) {
        prevSlide();
      } else if (e.keyCode === 32) {
        setClampedTarget(current);
      }
    };
  });

  return (
    
       setClampedTarget(index)}
      />
      
        
      
    
  );
}

至此我們可以看出,后面就是根據(jù)提交記錄的數(shù)據(jù)來進(jìn)行幻燈片的展示了,over。

git 命令

回到本地版最根本的功能上,搞了半天,根本的功能居然都不在 src 目錄下,而是在 cli 目錄下,而 cli 目錄的功能是通過起本地服務(wù)來進(jìn)行的。

回到之前的 runServer,我們?nèi)フ覍は?commitsPromise 是如何實(shí)現(xiàn)的:

const execa = require("execa");

async function getCommits(path) {
  const format = `{"hash":"%h","author":{"login":"%aN"},"date":"%ad"},`;
  const { stdout } = await execa("git", [
    "log",
    "--follow",
    "--reverse",
    `--pretty=format:${format}`,
    "--date=iso",
    "--",
    path
  ]);
  const json = `[${stdout.slice(0, -1)}]`;

  const messagesOutput = await execa("git", [
    "log",
    "--follow",
    "--reverse",
    `--pretty=format:%s`,
    "--",
    path
  ]);

  const messages = messagesOutput.stdout.replace(""", """).split(/
?
/);

  const result = JSON.parse(json)
    .map((commit, i) => ({
      ...commit,
      date: new Date(commit.date),
      message: messages[i]
    }))
    .slice(-20);

  return result;
}

async function getContent(commit, path) {
  const { stdout } = await execa("git", ["show", `${commit.hash}:${path}`]);
  return stdout;
}

module.exports = async function(path) {
  const commits = await getCommits(path);
  await Promise.all(
    commits.map(async commit => {
      commit.content = await getContent(commit, path);
    })
  );
  return commits;
};

核心的代碼都在 git.js 文件中了,而文件暴露的方法就是用來生成 commitsPromise 的。我們可以看到,本質(zhì)上就是通過 getCommits 和 getContent 拿到提交數(shù)據(jù),他們究竟做了什么呢?getCommits 主要執(zhí)行了 兩個(gè) git log 命令,而 getContent 則執(zhí)行的是 git show 命令。我只會(huì)些最近本的 git,來復(fù)習(xí)下這倆吧:

git log

該命令是為了在提交若干更新之后,又或者克隆了某個(gè)項(xiàng)目,想回顧下提交歷史時(shí)使用的。上面代碼中使用到的參數(shù)功能如下:

--follow
列舉單個(gè)文件的提交歷史列表(即使重命名)

-- reverse
逆序輸出選擇展示的提交記錄

----pretty=format:
按格式輸出

git show

通過 git show commitHash:filePath 可得到這次提交的文件內(nèi)容。

綜上,這個(gè)工具本地版 CLI 的實(shí)現(xiàn)原理,就是通過 git log 命令,找到指定文件的提交記錄,并遍歷 hash 或叫 tag 列表,利用 git show 拿到每次提交的時(shí)候文件的 content。

源地址:https://github.com/pomber/git...

一個(gè) React 優(yōu)化模式

今天有個(gè)推主提了一個(gè)優(yōu)化模式,我們來看推主給出的代碼:

function Foo({ children }) {
    const [x, updateX] = useState(0);
      return 
{ children }
; }

在上面的代碼中,如果我們以某種方式調(diào)用了 updateX,也就是說更新了 Foo 內(nèi)部的狀態(tài),組件就會(huì)重新 render。但是,因?yàn)?children 并沒有更新(引用相等),F(xiàn)oo 就不會(huì)嘗試著去重新 render children。

這樣做有什么價(jià)值呢?這樣寫的話我們就可以不用 React.memo 這類方法來避免計(jì)算開銷。我們可以寫一個(gè)更容易看清的例子來展示這種模式的作用:

可以看到,左邊的寫法中,父組件本身依賴組件內(nèi)部的一個(gè)計(jì)算,父子組件之間沒有關(guān)聯(lián)。當(dāng)點(diǎn)擊從而狀態(tài)更新結(jié)果后,App 需要重新 render,并且連帶著子組件一起重新渲染,這顯然存在性能浪費(fèi)。

而右邊的寫法中,計(jì)算邏輯被封裝在組件內(nèi)部了,其 children 屬性的引用沒有發(fā)生變化,這樣一來重新渲染子組件的開銷就可以節(jié)省掉了,也即 CompA 和 COMPB 并沒有重新 render。

此外,對 class 而言這個(gè)模式也是成立的,只要傳入的 children 不發(fā)生變化(引用相等)就可以。不過,我們的代碼通常會(huì)使 props 發(fā)生變化,所以通常情況下引用也不會(huì)相等。

源地址:https://twitter.com/sebmarkba...

「每日一瞥」是團(tuán)隊(duì)內(nèi)部日常業(yè)界動(dòng)態(tài)提煉,發(fā)布時(shí)效可能略有延后。

文章可隨意轉(zhuǎn)載,但請保留此 原文鏈接。

非常歡迎有激情的你加入 ES2049 Studio,簡歷請發(fā)送至 caijun.hcj(at)alibaba-inc.com 。
若有收獲,就賞束稻谷吧

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/101782.html

相關(guān)文章

  • 每日一瞥

    摘要:目前前端主要有以下四種方法會(huì)觸發(fā)對應(yīng)的回調(diào)方法方法客戶端回調(diào)客戶端回調(diào)參考地址每日一瞥是團(tuán)隊(duì)內(nèi)部日常業(yè)界動(dòng)態(tài)提煉,發(fā)布時(shí)效可能略有延后。 showImg(https://segmentfault.com/img/remote/1460000017975436?w=1200&h=630); 「ES2015 - ES2018」Rest / Spread Properties 梳理 Thr...

    xiangzhihong 評(píng)論0 收藏0
  • 每日一瞥

    摘要:另一部分就是類型的屬性,也就是返回的屬性。,一開始提案為,其作用就是遞歸的將數(shù)組展平到指定深度,默認(rèn)深度為。目前在使用時(shí),我們唯一的選擇是命令行界面。 showImg(https://segmentfault.com/img/remote/1460000017516912?w=1200&h=630); TypeScript 3.3 更新梳理 Object.assign vs Obje...

    劉明 評(píng)論0 收藏0
  • 每日一瞥

    摘要:當(dāng)引擎開始執(zhí)行腳本是的時(shí)候,會(huì)先創(chuàng)建一個(gè)全局執(zhí)行上下文,并將其到當(dāng)前執(zhí)行棧,無論何時(shí)一個(gè)函數(shù)被調(diào)用,就會(huì)創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文并壓入棧中。當(dāng)函數(shù)執(zhí)行完畢,執(zhí)行棧會(huì)將其彈出,并把控制權(quán)交給當(dāng)前棧的下一個(gè)上下文。 showImg(https://segmentfault.com/img/remote/1460000017516912?w=1200&h=630); 從過去直到 Reac...

    qujian 評(píng)論0 收藏0
  • 每日 30 秒 ? 大家一起被捕吧

    showImg(https://segmentfault.com/img/remote/1460000018793640?w=900&h=500); 簡介 安全、注入攻擊、XSS 13歲女學(xué)生被捕:因發(fā)布 JavaScript 無限循環(huán)代碼。 這條新聞是來自 2019年3月10日 很多同學(xué)匆匆一瞥便滑動(dòng)屏幕去看下一條消息了,并沒有去了解這段代碼是什么,怎么辦才能防止這個(gè)問題。事情發(fā)生后為了抗議日本...

    lbool 評(píng)論0 收藏0
  • Django靜態(tài)文件一瞥

    摘要:配置在設(shè)置項(xiàng)中確認(rèn)包含增加設(shè)置項(xiàng),值為一個(gè)字符串路徑,必須以結(jié)尾在模板中這樣引用在的目錄存放靜態(tài)文件開發(fā)期間使用極度低效時(shí)有別的做法注意默認(rèn)為,一個(gè)列表,表示獨(dú)立于的靜態(tài)文件存放位置。 配置 1.在INSTALLED_APPS設(shè)置項(xiàng)中確認(rèn)包含django.contrib.staticfiles 2.增加STATIC_URL設(shè)置項(xiàng),值為一個(gè)字符串(路徑),必須以‘/’結(jié)尾 3.在模板中...

    binta 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

XboxYan

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<