かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Homebrew brew update で Error: homebrew-core is a shallow clone

開発環境アップデートしたからコマンド実行して〜って言われてやろうとしたら brew update でエラーになった。

$ brew update
Error: homebrew-core is a shallow clone. To `brew update` first run:
  git -C "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core" fetch --unshallow

このコマンド叩いてね。って出てるけど、そのままコピペしても何も起こらない…

git の fetch が出来てないから先に fetch する必要があった

--unshallow なしのコマンドで先に fetch すれば OK

$ git -C "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core" fetch
# 完了したら --unshallow オプション付きで実行
$ git -C "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core" fetch --unshallow
remote: Enumerating objects: …
# 完了したら brew update できるようになっている
$ brew update

おわり。

開発環境の再構築だいたいハマるので docker になってもあまり変わらないイメージ…


[参考]

みんなでアジャイル ―変化に対応できる顧客中心組織のつくりかた

みんなでアジャイル ―変化に対応できる顧客中心組織のつくりかた

  • 作者:Matt LeMay
  • 発売日: 2020/03/19
  • メディア: 単行本(ソフトカバー)

JavaScript 配列の要素を immutable に変更する方法を考えてみる

よくあるオブジェクトが要素のリストでオブジェクトの内容を変更したい。
そして配列は immutable に扱いたい。

interface Idol {
  id: number;
  name: string;
  type: string;
  unit?: string;
};

const data: Idol[] = [
  { id: 1, name: 'Hosimiya Ichigo', type: 'cute', unit: 'soleil' },
  { id: 2, name: 'Kiriya Aoi', type: 'cool', unit: 'soleil' },
  { id: 3, name: 'Shibuki Ran', type: 'sexy' },
  { id: 4, name: 'Todo Yurika', type: 'cool' },
  { id: 5, name: 'Kanzaki Mistuki', type: 'sexy' },
  { id: 6, name: 'Nastuki Mikuru', type: 'pop' },
  { id: 7, name: 'Ozora Akari', type: 'cute' },
];

map で変更する

const updateList = (list) => (id, data) => {
  return list.map((item) => {
    if (item.id === id) {
      return {
        ...item,
        ...data,
      }
    }
    return item;
  });
};

const newData = updateList(data)(3, { unit: 'soleil' });
/*
[
  {id: 1, name: "Hosimiya Ichigo", type: "cute", unit: "soleil"},
  {id: 2, name: "Kiriya Aoi", type: "cool", unit: "soleil"},
  {id: 3, name: "Shibuki Ran", type: "sexy", unit: "soleil"},
  {id: 4, name: "Todo Yurika", type: "cool"},
  {id: 5, name: "Kanzaki Mistuki", type: "sexy"},
  {id: 6, name: "Nastuki Mikuru", type: "pop"},
  {id: 7, name: "Ozora Akari", type: "cute"}
]
*/

配列をすべてループで回すので扱うデータが大きいとパフォーマンスに影響がありそうだけど、シンプルにデータの変更ができる

更新するデータのインデックスを取得して、更新データの前後を slice で取り出して結合する

const updateList = (list) => (id, data) => {
  const index = list.findIndex((item) => item.id === id);
  // 変更対象がない時はコピーした元データを返す
  if (index === -1) {
    return [...list];
  }
  
  return [
    ...list.slice(0, index),
    {
      ...list[index],
      ...data,
    },
    ...list.slice(index + 1),
  ];
};

updateList(data)(7, { unit: 'luminas' });
/*
[
  {id: 1, name: "Hosimiya Ichigo", type: "cute", unit: "soleil"},
  {id: 2, name: "Kiriya Aoi", type: "cool", unit: "soleil"},
  {id: 3, name: "Shibuki Ran", type: "sexy"},
  {id: 4, name: "Todo Yurika", type: "cool"},
  {id: 5, name: "Kanzaki Mistuki", type: "sexy"},
  {id: 6, name: "Nastuki Mikuru", type: "pop"},
  {id: 7, name: "Ozora Akari", type: "cute", unit: "luminas"}
]
*/

Array.prototype.slice メソッドは取り出せる要素がない時、空配列 [] を返すので変更対象が配列の先頭でも最後でもエラーにならない

e.g.

const list = [1, 2, 3, 4, 5]
list.slice(0, 0); // => []
list.slice(list.length); // => []

id をキーにしたオブジェクトを作成して、変更後に配列に戻す

// 配列を key をキーにしたオブジェクトに変換する
const mapToObjectbyKeys = (list) => (key) => {
  return list.reduce((obj, item) => {
    return {
      ...obj,
      [item[key]]: {...item}
    }
  }, {});
};

// キーのリスト順にオブジェクトを配列に戻す
const objectToArrayByList = (obj) => (keyList) => {
  return keyList.map((key) => {
    return {...obj[key]};
  });
};

const updateList = (list) => (key, id, data) => {
  // key のリストを作成
  const keyList = list.map((item) => item[key]);
  // key をキーにしたオブジェクトを作成
  const dataObject = mapToObjectbyKeys(list)(key);
  // データを更新したオブジェクトを作成
  const updateData = {
    ...dataObject,
    [id]: {
      ...dataObject[id],
      ...data,
    }
  };
  // 配列に戻して返す
  return objectToArrayByList(updateData)(keyList);
};

updateList(data)('id', 5, { unit: 'tristar' });
/*
[
  {id: 1, name: "Hosimiya Ichigo", type: "cute", unit: "soleil"},
  {id: 2, name: "Kiriya Aoi", type: "cool", unit: "soleil"},
  {id: 3, name: "Shibuki Ran", type: "sexy"},
  {id: 4, name: "Todo Yurika", type: "cool"},
  {id: 5, name: "Kanzaki Mistuki", type: "sexy", unit: "tristar"},
  {id: 6, name: "Nastuki Mikuru", type: "pop"},
  {id: 7, name: "Ozora Akari", type: "cute"}
]
*/

やりたいことに対して壮大すぎる気がする…
内部的なデータはオブジェクトに変換したもので持っておき、インデックス順に並べて表示する時だけ配列に戻すという運用方法ならありかもしれない。

関数内で配列のコピーを作成してしまう

immutable 原理主義でないなら配列を丸っとコピーして、関数内で破壊的なメソッドを使って変更して返してしまえば簡単そう

コピーした配列を直接操作する

const updateList = (_list) => (id, data) => {
  const list = [..._list];
  const index = list.findIndex((item) => item.id === id);
  if (index === -1) {
    return list;
  }
  
  const item = list[index];
  list[index] = {
    ...item,
    ...data,
  };
  return list;
};

updateList(data)(6, { unit: 'WM' });
/*
[
  {id: 1, name: "Hosimiya Ichigo", type: "cute", unit: "soleil"},
  {id: 2, name: "Kiriya Aoi", type: "cool", unit: "soleil"},
  {id: 3, name: "Shibuki Ran", type: "sexy"},
  {id: 4, name: "Todo Yurika", type: "cool"},
  {id: 5, name: "Kanzaki Mistuki", type: "sexy"},
  {id: 6, name: "Nastuki Mikuru", type: "pop", unit: "WM"},
  {id: 7, name: "Ozora Akari", type: "cute"},
]
*/

splice を使うパターン

const updateList = (_list) => (id, data) => {
  const list = [..._list];
  const index = list.findIndex((item) => item.id === id);
  if (index === -1) {
    return list;
  }
  // 置き換えられる要素が返される
  list.splice(index, 1, {
    ...list[index],
    ...data,
  });
  return list;
};

updateList(data)(1, { unit: 'Cosmos' });
/*
[
  {id: 1, name: "Hosimiya Ichigo", type: "cute", unit: "Cosmos"},
  {id: 2, name: "Kiriya Aoi", type: "cool", unit: "soleil"},
  {id: 3, name: "Shibuki Ran", type: "sexy"},
  {id: 4, name: "Todo Yurika", type: "cool"},
  {id: 5, name: "Kanzaki Mistuki", type: "sexy"},
  {id: 6, name: "Nastuki Mikuru", type: "pop"},
  {id: 7, name: "Ozora Akari", type: "cute"}
]
*/

Array.prototype.splce() は破壊メソッドで、戻り地は変更される値なのでそのまま return はできない

for break を使う

配列をコピーしてしまっているので for で回して変更対象のデータを置き換えたら break してしまうのもアリかも

const updateList = (_list) => (id, data) => {
  const list = [..._list];

  for(let i = 0, l = list.length; i < l; i += 1) {
    if (list[i].id === id) {
      list[i] = {
        ...list[i],
        ...data,
      };
      break;
    }
  }
  return list;
};

updateList(data)(4, { name: 'Arisugawa Otome', type: 'pop' });
/*
[
  {id: 1, name: "Hosimiya Ichigo", type: "cute", unit: "soleil"},
  {id: 2, name: "Kiriya Aoi", type: "cool", unit: "soleil"},
  {id: 3, name: "Shibuki Ran", type: "sexy"},
  {id: 4, name: "Arisugawa Otome", type: "pop"},
  {id: 5, name: "Kanzaki Mistuki", type: "sexy"},
  {id: 6, name: "Nastuki Mikuru", type: "pop"},
  {id: 7, name: "Ozora Akari", type: "cute"}
]
*/

伝統的な書き方って感じだけどOK。

所管

データがめちゃめちゃ大きくならないなら map を使うのがわかりやすそう。
それか配列を丸っとコピーしてしまって、findIndex で変更データを探して置き換えるパターンが見やすいのかな?

Array.prototype.splice が変更された新しい配列を返してくれるメソッドなら楽だったのにな…って感じ。
lodash とかライブラリを使うといい感じに操作できる関数があるのかもしれない。

TODOアプリとか結構あるあるなデータ変更だと思うんだけど、みんなどうしてるのか教えて〜

おわり


[参考]

ハンズオンJavaScript

ハンズオンJavaScript

道に迷ったときはアイカツ!を見よう

React v17 create-react-app で作ったアプリで ESLint に怒られまくった

npx create-react-app で React のアプリを作ったら v17 系になっていました。
今までのプロジェクトで使っていた ESLint の設定を持ってきたらエラーになる部分があったのでメモ

  • react 17.0.1
  • react-scripts 4.0.1
  • eslint 7.14.0
  • eslint-plugin-react 7.21.5

.eslintrc.js (前使ってたのをコピペして持ってきた)

module.exports = {
  env: {
    browser: true,
    es6: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:prettier/recommended',
    'prettier',
    'prettier/react',
    'prettier/standard',
  ],
  globals: {
    Atomics: 'readonly',
    SharedArrayBuffer: 'readonly',
  },
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  plugins: ['react', 'prettier'],
  root: true,
  rules: {
    'eol-last': ['error', 'always'],
    'indent': ['error', 2, { SwitchCase: 1 }],
    'newline-before-return': 'error',
    'no-console': 'warn',
    'no-dupe-class-members': 'error',
    'no-var': 'error',
    'object-shorthand': ['error', 'always'],
    'prefer-arrow-callback': 'error',
    'prefer-const': 'error',
    'prefer-spread': 'error',
    'react/prop-types': 'off',
    'require-yield': 'error',
  },
};

JSX で React をインポートしてないと 'React' must be in scope when using JSX エラー

React v17 からは JSX を使っているファイルに import React from 'react'; を書かなくてもOKになっていました。(使ってるように見えない React を import しなくて良くなったのは個人的には嬉しい)

従来、JSX記法を使用するファイルでは以下の記述を含める必要がありました。

import React from "react";
これは、JSXが React.createElement に変換されるのであらかじめこちら側で React を用意しておく必要があったからです。新しい記法では、React をあらかじめインポートしておく必要がありません。 cf. [https://zenn.dev/uhyo/articles/react17-new-jsx-transform:title]

React v17 からは JSX を React.createElement の形式ではなく、動的にインポートされる _jsxs を使って変換するようになったので React をインポートしなくても済むようになったみたいです。
しかし ESLint で plugin:react/recommended を指定していると JSX で import React from 'react' が無いとエラーになってしまっていました。

.eslintrc.js

module.exports = {
  //...
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    // …
  ],
  // … 
}

create-react-app で作られた App.js には import React from 'react' が無いのでエラーになる。

// App.js
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      {/* 略 */}
    </div>
  );
}

=> error 'React' must be in scope when using JSX

react/react-in-jsx-scope のルールを off にする

v17 以前は import React from 'react' が無いとエラーになっていたので、おそらく書き忘れ防止のルールだと思うのですが、react/react-in-jsx-scope が recommended で有効になっているので、これを off にしてあげれば OK

.eslintrc.js

module.exports = {
  // …
  rules: {
    // …
   'react/react-in-jsx-scope': 'off',
  },
}

cf. eslint-plugin-react/react-in-jsx-scope.md at edbbd7931902ec0b1cc1941df4155e39acc06f31 · yannickcr/eslint-plugin-react · GitHub

₍ ᐢ. ̫ .ᐢ ₎👌


テストファイルで 'test' is not defined, 'expect' is not defined のエラー

Jest が使われているテストファイルで、Jest の 関数が定義されてないというエラー

// src/App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/iu);
  expect(linkElement).toBeInTheDocument();
});

=> 'test' is not defined
=> 'expect' is not defined

Jest を import してるわけでもないので、エラーが出るのもまぁ解るって感じ。

Jest の関数を global にすれば OK

$ npm install --save-dev eslint-plugin-jest

.eslintrc.js

module.exports = {
  env: {
    browser: true,
    es6: true,
    node: true,
    'jest/globals': true, // ←追加
  },
  // …
  plugins: ['react', 'prettier', 'jest'], // 'jest' を追加
};

cf. eslint-plugin-jest - npm

₍ ᐢ. ̫ .ᐢ ₎👌


Dynamic import で import' and 'export' may only appear at the top level エラー

生成された reportWebVitals.js 内で Dynamic import が使われていてこれがエラーになっていました。

// reportWebVitals.js
const reportWebVitals = (onPerfEntry) => {
  if (onPerfEntry && onPerfEntry instanceof Function) {
    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      // ...
    });
  }
};

=> error Parsing error: 'import' and 'export' may only appear at the top level

parserOptions ecmaVersion 2020 を指定

parserOptions.ecmaVersion2020 にすれば Dynamic imports がサポートされる

.eslintrc.js

module.exports = {
  // …
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
-   ecmaVersion: 2018,
+   ecmaVersion: 2020,
    sourceType: 'module',
  },
}

cf. ESLint v6.2.0 - Qiita

₍ ᐢ. ̫ .ᐢ ₎👌

babel-eslint を使う

検索すると parser に babel-eslint を使う方法が結構ヒットする。
ESLint が ES2020 をサポートする以前は babel-eslint を使うことで Dynamic imports を通していたっぽい。
parserOptionsecmaVersion: 2018 でも babel-eslint の設定をすれば Dynamic imports を使ってもエラーにならないようにできた。

$ npm install --save-dev babel-eslint

.eslintrc.js

module.exports = {
  parser: 'babel-eslint', // ← 追加
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  // …
}

eslint v6.2.0 以降では parserOptions.ecmaVersion: 2020 とすれば Dynamic imports 大丈夫なので、2020 に出来ない理由がない限りこの方法は使わなくても良さそう。(create-react-app で作ったアプリは内部的に babel-eslintを使ってる)

所管

これで creat-react-app で作ったままのアプリで ESLint が通るようになりました!
webpack もだけど設定ファイルって一回作ったらコピペで使いまわしちゃうから、変更が入ってエラーが出てしまうと毎回これ何の設定だったっけ〜ってなってしまう。(記憶力0)
設定を作る機会が少ないから記憶に定着しないんだよね…って言い訳させてくれ。


[参考]

ESLint 関連の記事に Lindt 貼りがち〜