0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

react メモ

Last updated at Posted at 2021-04-20

ディレクトリ

- src
    // プロジェクトによって変えればok
    // 各componentはContainerとPresentationで分けるとPresentationはpropsを利用すればいいだけになる
    - components // 以下各ディレクトリで使うもの
        - index.ts // ContainerComponetをexport
        - hooks.ts // ロジックを分離, テストできるようにする, useDispatch, useStateなど
        - ${component名}Container.tsx // hooksやpropsを受け取り${component名}.tsxに渡す
        - ${component名}.tsx // Presentational Component
    - enums
    - consts
    - hooks // 共通hooks定義
    - models
        - ${domain名.ts}
    - redux
        - ${domain名}
            - actions.ts // thunkのみ
            - index.ts // adapter, initialState, reducer, selector, Stateの型, actions(actionとthunkActionをマージ)をexport
            - module.ts // slice
            - selectors.ts
            - types.ts // actionのname spaceとthunkの型をexport
    - router
        - index.tsx // 各pagesへのrouter定義, lazyで読み込んだほうが軽い
        - AuthRouterなど
    - repositories
    - utils

型定義(TypeScript)

下の定義を利用して上にtypeを拡張していくと、修正や拡張がしやすい

repository層

  • apiのデータを表示用に加工してしまうとstoreにその値が反映されてしまう
    • storeには純粋なデータを保持、各セレクターで取得時に加工するなど

エラーハンドリング

  • apiClientなどでエラーハンドリングして加工してから再スローすると、他の箇所でエラーハンドリングの処理が書きやすい
  • エラーハンドリングの処理を共通化できるもの
    • reduxのactionで書いちゃう
  • 分岐するもの
    • hooks内でそれぞれハンドリング処理を書き、Componentで呼び出しするとよい
      unwrapを使うとthunkの結果をそのまま受け取れる
      thunk内でのエラーのスローも、rejectedWithValueもtry-catchで受けれる
      try {
        await dispatch(todoActions.getTodosAsync()).unwrap()
      } catch (e) {
        // error handling
      }

* thunk内でエラー時の挙動

  • エラーをスローした場合
    • reducerのactionにはerrorに値が格納されてくる
  • rejectedWithValueをreturnした場合
    • reducerのactionにはpayloadに値が格納されてくる

useeffect

  • 早期return

ref

stateが絡むhooks

custom hooks内で他のhooksのstate更新をする場合

  • state更新をする関数を渡してあげないとダメ
const useCount = () => {
  const [count, setCount] = useState(0)
}

redux

type

action

ActionのPayloadのtypeについて

slice.js
const counterSlice = createSlice({
  // ...
  reducers: {
    incrementByAmount(state, action) {
      // ...
    },
  },
})

export const { incrementByAmount } = counterSlice.actions
type IncrementByAmountParam = ReturnType<typeof incrementByAmount>['payload']

thunk

型定義をexportする

selector

  • stateの参照のみはselectorの切り出しをしないでいい
  • ロジック含む、配列処理(map, filter)などは検証や重い処理になってしまう場合があるのでselectorに切り出し
    • createSelectorを利用
    • inputSelectorsが返す値が前回と同じだった場合は、resultFunction を呼ぶことなく、キャッシュした前回の値を返す
    • キャッシュ数は1

https://9oelm.github.io/2020-09-13--How-to-make-useSelector-not-a-disaster/
https://scrapbox.io/mrsekut-p/createSelector
https://scrapbox.io/mrsekut-p/useSelector

caseReducer

https://redux-toolkit.js.org/api/createSlice#examples
https://stackoverflow.com/questions/67792374/react-redux-alerting-best-practices

reducerの中で他のreducerをcallできる

  reducers: {
    addTodo: (state, action: PayloadAction<Omit<Todo, 'id' | 'isDone'>>) => {
      const id =
        state.todos.map((todo) => todo.id).reduce((a, b) => Math.max(a, b), 0) +
        1
      const updatedTodo = { ...action.payload, id, isDone: false }
      const updatedTodos = state.todos.concat(updatedTodo)
      state.todos = updatedTodos
      const deleteTodoAction = slice.actions.deleteTodo(1)
      slice.caseReducers.deleteTodo(state, deleteTodoAction) // ここ
    },

また、他のsliceのactionもcallできる
uiStateのloadingとかもcomponent側ではなく、reducer側で処理を共通でかけたりもできる

const slice = createSlice({
  name: 'ui',
  initialState: {
    loading: false,
  },
  reducers: {
    updateLoading: (state, action: PayloadAction<{ loading: boolean }>) => {
      state.loading = action.payload.loading
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(todoActions.getTodosAsync.pending, (state, _action) => {
        const action = slice.actions.updateLoading({ loading: true })
        slice.caseReducers.updateLoading(state, action)
      })
      .addMatcher(isFulfilled(todoActions.getTodosAsync), (state, _action) => {
        const action = slice.actions.updateLoading({ loading: false })
        slice.caseReducers.updateLoading(state, action)
      })
      .addMatcher(isRejected(todoActions.getTodosAsync), (state, _action) => {
        const action = slice.actions.updateLoading({ loading: false })
        slice.caseReducers.updateLoading(state, action)
      })
  },
})

reselector

引数を渡す(不要かも)

※ただし、引数が変わるとstateが変わっていなくても再レンダリングかかる(キャッシュ数1なので、頻繁に再レンダリングされちゃいそう)

これがわかりやすい

こっちには普通に書いてるけどダメっぽい

custom hooks

useref

コンポーネントのDOMにセットしたrefをhooks内で使用する場合

  1. hooks内でrefを作成してそれをreturnしてあげる
  2. 利用側はhooksから受けたrefをセットしてあげればOK

メリット

利用側が毎回refを生成しないで済む

useref vs callback ref

callback ref

React が ref を DOM ノードにアタッチまたはデタッチするときにコードを実行したい場合は、callback refを使うといい

  • refはcurrentが更新されても通知を受けれない
  • callback refを使うと後から、クリックなどでrefに指定した要素が後から表示された場合でも変更を受け取ることができる

ここ読んでみる
https://www.robinwieruch.de/react-ref

test

custom hooks

  • 非同期処理は、react-testing-libraryのwaitForを使うこと
    使わないと、処理が終わる前にexpectが走ってしまう

tips

selectのoptionなどの値はconst定義

export const Role = {
  USER: {
    code: 1,
    label: 'ユーザー'
  },
  ADMIN: {
    code: 2,
    label: '管理者'
  },
  OWNER: {
    code: 3,
    label: 'オーナー'
  },
  UNKNOWN: {
    code: 0,
    label: ''
  }
} as const;
export type RoleKey = keyof typeof Role;
export type RoleValue = typeof Role[RoleKey];

export const findRoleByCode = (
  code: number
): RoleValue =>
  Object.values(Role).find(p => p.code === code) ||
  Role.UNKNOWN;

利用側では

Object.values(Role)

で[{code:"",label:""}]がとれる

DFlex

import * as React from 'react';
import styled from '@emotion/styled';
import { css } from '@emotion/react';

type BoxProps = {
  children: React.ReactNode;
  direction?: string;
  justify?: string;
  align?: string;
} & React.InputHTMLAttributes<HTMLDivElement>;
const Wrapper = styled.div<Omit<BoxProps, 'children'>>`
  display:flex;
  ${props => css`
    flex-direction: ${props.direction ?? 'row'};
  `}
  ${props =>
    css`
      align-items: ${props.align ?? 'center'};
    `}
  ${props => css`
    justify-content: ${props.justify ?? 'start'};
  `}
`;

export type DFlexProps = BoxProps;
export const DFlex: React.VFC<DFlexProps> = React.memo(
  ({ children, ...props }) => {
    return <Wrapper {...props}>{children}</Wrapper>;
  }
);
DFlex.displayName = 'DFlex';

※reactのcomponentだと型エラーでcomponent selectorが使えないので、atomはmemoでwrapしない
https://qiita.com/kotarella1110/items/a243c7bde41fad23343f

react-tooltip

offsetと吹き出しの矢印の位置変更

                <StyledTooltip
                  tooltipId={'testId'}
                  html={true}
                  width={`${TOOLTIP_WIDTH}px`}
                  offset={{ right: TOOLTIP_WIDTH / 2 - TOOLTIP_ARROW_WIDTH }}
                />

const StyledTooltip = styled(Tooltip)`
  &:after {
    /* 三角吹き出しの位置調整 */
    left: ${TOOLTIP_ARROW_WIDTH}px !important;
  }
`;
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?