Closed14

JavaScriptとReactを学ぶ

アロー関数のスコープについて

アロー関数には、内部にスコープを持つ事ができない。
なので、アロー関数の中でthisをした場合、そのアロー関数を持つオブジェクトを指す。(関数式で作られる関数オブジェクトを指す訳ではない)

const drinks = {
  types: ['coke', 'soda', 'beers', 'water'],
  serve: function () {
    setTimeout(function () {
      console.log(this.types)
    }, 1000)
  }
}

これだと、serveの中のthisは、setTimeoutの中のfunctionのスコープの中なので
typesを参照できない。

const drinks = {
  types: ['coke', 'soda', 'beers', 'water'],
  serve: function () {
    setTimeout(() => { // ← ここ
      console.log(this.types)
    }, 1000)
  }
}

こうする事で、serveを持つdrinksのオブジェクトを参照する事ができる。

const drinks = {
  types: ['coke', 'soda', 'beers', 'water'],
  serve: () => { // ← ここ
    setTimeout(() => {
      console.log(this.types)
    }, 1000)
  }
}

serveもアロー関数にすると、serveのスコープも外れるので、さらに上の層のオブジェクトを参照する(ブラウザであればwindowなど)

デストラクチャリングの仕方種類

デストラクチャリングとは

オブジェクトの中身を変数に入れる時に、必要なプロパティだけを取り出すこと

パターン1

const books = {
  title: 'my book',
  pages: 130,
  price: 1500,
  index: ['header', 'body', 'footer']
}

const { title, pages } = books

パターン2

関数の引数でデストラクチャリングを行う

const showTitle = ({ title }) => {
  console.log(title) // my book
}

const books = {
  title: 'my book',
  pages: 130,
  price: 1500,
  index: ['header', 'body', 'footer']
}

showTitle(books) // my book

パターン3

ネストがもう一段深いオブジェクトに対するデストラクチャリング

const showTitle = ({ author: { name } }) => {
  console.log(title) // nanana
}

const books = {
  title: 'my book',
  pages: 130,
  price: 1500,
  index: ['header', 'body', 'footer'],
  author: {
    name: 'nanana',
    age: 50,
    language: 'jp'
  }
}

showTitle(books) // nanana

イミュータブルな配列を実現するためのスプレッド構文

const array = ['item1', 'item2', 'item3']
const newArray = [...array].reverse()

高階関数

コールバック関数を返すコールバック関数のこと
以下参考

const callback1 = () => () => {
  // 処理
}

こうすることで、最初のコールバックで引数に持たせたものを処理内で保持しつつ
2つ目のコールバックを実行することができる。

const callback1 = arg1 => arg2 => {
  // 処理
}

const callback2 = callback1('test')
callback2('test2')

というようにすることでcallback1に渡す引数をcallback2でも引き継ぐことができる。

高階関数を使った再帰処理

const compose = (...fns) => arg => {
  fns.reduce((composed, f) => f(composed), arg)
}

複数のファンクション(fns)を引数にとり、次の引数が渡されるのを待つコールバックを作る。
reduceによって、第一引数が蓄積引数で、第二引数に通常の引数(ここではコールバック関数)を取る。
次の引数(arg)が渡ってきたとこで、それを初期値としてコールバックを適応していく事ができる。

こうすることでいくつになってもコールバックを受け取る事ができる、スケールのしやすい高階関数を作ることができている。

数を指定して、スケールしやすいコンポーネントの作り方

export default function circle({ num = 5 }) {
  return [...Array(num)].map((_, i) => <Circle key={i}>)
}

Arrayによって、指定した数の領域が確保された配列が作られる。
しかし、配列の中身の値自体は何も入っていない状態なのでundefinedになる。
そのため、最初のコールバックの引数には_が入る。
indexだけ使用して、keyとして使用する。

ステート管理について

アプリケーション内の全てのコンポーネントがステートを持つというのは良くない。
そのため、ステートは一箇所で管理した方が良いという考え。

ステートを持たない関数コンポーネントは純粋関数である。

createContextについて

createContext作られるコンテキストのProviderという機能を使うことで
Provider配下の子コンポーネントにステートを伝播させることができる。

Provider側
export const TestContext = createContext()

render(
  <TestContext.Provider value={{ value }}>
    <App />
  </TestContext.Provider>,
  document.getElementById('root')
)
コンポーネント(useContext)側
import { TestContext } from 'CONTEXT_PATH'

export default function Test() {
  const { value } = useContext(TestContext)
  // valueをJSX内などで使うことができる。
}

しかし上記では、valueの取得(データへのアクセス)は可能になるが
変更は行うことができない。

そのため、カスタムプロバイダーを作る必要がある。

Provider
export const TestContext = createContext()

export default function TextProvider({ children }) {
  const [test, setTest] = useState(initialTest)
  return (
    <TestContext.Provider value={{ test, setTest }}>
      { children }
    </TestContext.Provider>
  )
}

しかし、上記のままでもまだ不完成で
setTestだけでオブジェクトを変更可能にしてしまうと、どこでもどのプロパティに対しても変更ができてしまう。
そのため、特定のデータのみを変更する関数を作っておくのが良い。
非破壊的変更での変更を行う関数を行うことが重要。
以下例

const addTest = (newProperty, test) => {
  setTest({
    ...test,
    newProperty
  })
}

全ての関数をProviderで伝播する必要があります。

useEffectについて

useEffectのreturnにコールバック関数を返すと、コンポーネントが削除される時に実行される。

useEffect(JavaScript)の副作用

useEffectの第二引数に配列でステートや変数を渡すと、そのvalueが変更される度に実行される、逆に言えば変更されない場合実行されない。
しかし、これはプリミティブの場合で、配列を変数に格納して第二引数に渡すと、変更がなくても実行される。

例えば、以下のよう

const array1 = ['aaa', 'bbb', 'ccc']
const array2 = ['aaa', 'bbb', 'ccc']

array1 === array2 // false

これを回避するには、コンポーネントを構成する関数の外で配列を宣言しておくことで回避できる。

const array1 = ['aaa', 'bbb', 'ccc']

export function Component() {

  useEffect(() => {
      // 処理
  }, [array1])

  return (
    <div></div>
  )
}

コンポーネント関数内で配列などを使わないといけない場合

propsなどから配列を作る必要がある場合
useMemoを使うことでキャッシュし、同じ引数が使われた場合メモ化されたものを使うことができる。

const memoArray = useMemo(() => {
  const array = ['aaa', 'bbb', 'ccc'] // 本来であれば関数内でしか作れない配列
  return array
}, [])

useCallback

useMemoは配列とかインスタンスに適応するが
useCallbackはファンクションをメモ化するもの

useLayoutEffect

useEffectより先に実行される。
コンポーネントの描画関数が呼ばれるより 後
ブラウザのPaint処理より 前
に実行される。

フックを使う上でのルール

  1. コンポーネントのスコープで実行する
  2. 多くのことを一つのフックで行うのではなく複数に分ける
  3. フックはトップレベルで呼び出す(条件やループの中では呼び出せない)
  4. Promiseは、useEffect内に即時関数でresolveする

useReducerについて

基本的にJavaScriptのreduceと同じ挙動になる。
一つ目の引数には、コールバック
二つ目の引数には、初期値

コールバックには二つの引数をとる。
一つ目は、現在の蓄積値など
二つ目は、次点の値など

使い道

複雑なオブジェクトを変更する時に使うのが良い

const [user, setUser] = useReducer(
  (user, newDetails) => ({ ...user, ...newDetails }),
  firstUser
)

こうすることで、userオブジェクトの特定のプロパティだけを変更することができる。
変更するには以下のようにするだけでよい

setUser({ name: 'testUser' })

predicateとは

返り値に真偽値を返すこと

'abc' === 'abc' // true

このようにわざわざreturn trueを返さずとも、trueになる処理のことを指す

外部APIの呼び出し

コンポーネントの上から、子コンポーネントに対して
子コンポーネント内で実行されるfetchに対しては逐次実行されていくが
並列でまとめられたコンポーネントに関しては並列実行がされていく。
本来であれば並列実行の方が、リクエストの取得時間的なパフォーマンスの点でも、コードの複雑さ的にも良い。

export default function App() {
  return (
    <Component1></Component1> // <- この中で別のコンポーネントをネストして、APIの呼び出しを行う(逐次実行)
  )
}

export default function App() {
  return (
    <>
      <Component1></Component1> // <- それぞれでAPIの呼び出しを行う(並列実行)
      <Component2></Component2> // <- それぞれでAPIの呼び出しを行う(並列実行)
      <Component3></Component3> // <- それぞれでAPIの呼び出しを行う(並列実行)
    </>
  )
}
このスクラップは2021/08/09にクローズされました
ログインするとコメントできます