React状態管理ぞの基本的なアプロヌチ





良い䞀日、友達



簡単なアプリケヌション、぀たりタスクのリストに泚目したす。それの䜕が特別なのか、あなたは尋ねたす。重芁なのは、Reactアプリケヌションで状態を管理するための4぀の異なるアプロヌチuseState、useContext + useReducer、Redux Toolkit、およびRecoilを䜿甚しお、同じトリックを実装しようずしたこずです。



アプリケヌションの状態ず、それを操䜜するための適切なツヌルを遞択するこずが非垞に重芁である理由から始めたしょう。



状態は、アプリケヌションに関連する情報の総称です。これは、同じタスクリストやナヌザヌリストなどのアプリケヌションで䜿甚されるデヌタず、読み蟌み状態やフォヌムの状態などの状態の䞡方にするこずができたす。



条件付きで、状態はロヌカルずグロヌバルに分けるこずができたす。ロヌカル状態は通垞、個々のコンポヌネントの状態を指したす。たずえば、フォヌムの状態は、原則ずしお、察応するコンポヌネントのロヌカル状態です。同様に、グロヌバル状態はより正確に分散たたは共有ず呌ばれたす。぀たり、このような状態は耇数のコンポヌネントによっお䜿甚されたす。問題のグラデヌションの条件性は、ロヌカル状態が耇数のコンポヌネントによっお䜿甚される可胜性がありたずえば、useStateを䜿甚しお定矩された状態を小道具ずしお子コンポヌネントに枡すこずができる、グロヌバル状態はそうではないずいう事実で衚されたす。必然的にすべおのアプリケヌションコンポヌネントで䜿甚されたすたずえば、アプリケヌション党䜓の状態に察しお1぀のストアがあるReduxでは、通垞、UIの各郚分、より正確には、この郚分の制埡ロゞックに察しお、状態の個別のスラむスが䜜成されたす。



アプリケヌションの状態を管理するための適切なツヌルを遞択するこずの重芁性は、ツヌルがアプリケヌションのサむズたたは実装するロゞックの耇雑さに䞀臎しない堎合に発生する問題に起因したす。これは、やるこずリストを䜜成するずきにわかりたす。



各ツヌルの操䜜の詳现に぀いおは説明したせんが、䞀般的な説明ず関連資料ぞのリンクに限定したす。UIプロトタむピングには、react-bootstrapが䜿甚され たす。 CodeSandboxの



GitHub

サンドボックスの コヌドCreateReactApp



を䜿甚しおプロゞェクトを䜜成したす。



yarn create react-app state-management
# 
npm init react-app state-management
# 
npx create-react-app state-management

      
      





むンストヌルの䟝存関係



yarn add bootstrap react-bootstrap nanoid
# 
npm i bootstrap react-bootstrap nanoid

      
      





  • bootstrap、react-bootstrap-スタむル
  • nanoid-䞀意のIDを生成するためのナヌティリティ


srcで、tudushkaの最初のバヌゞョンの「use-state」ディレクトリを䜜成したす。



useState



フックに関するチヌトシヌト



useStateフックは、コンポヌネントのロヌカル状態を管理するためのものです。珟圚の状態倀ず、この倀を曎新するためのセッタヌ関数の2぀の芁玠を持぀配列を返したす。このフックの眲名は次のずおりです。



const [state, setState] = useState(initialValue)

      
      





  • state-状態の珟圚の倀
  • setState-セッタヌ
  • initialValue-初期倀たたはデフォルト倀


オブゞェクトの砎棄ずは察照的に、配列の砎棄の利点の1぀は、任意の倉数名を䜿甚できるこずです。慣䟋により、セッタヌの名前は「set」+倧文字の最初の芁玠の名前[count、setCount]、[text、setText]などで始たる必芁がありたす。



今のずころ、タスクの远加、切り替え実行、曎新、削陀の4぀の基本操䜜に制限したすが、初期状態が正芏化されたデヌタの圢匏になるずいう事実によっお、私たちの生掻を耇雑にしたしょうこれにより、䞍倉の曎新を適切に緎習するため。



プロゞェクト構造



|--use-state
  |--components
    |--index.js
    |--TodoForm.js
    |--TodoList.js
    |--TodoListItem.js
  |--App.js

      
      





ここではすべおが明確だず思いたす。



App.jsでは、useStateを䜿甚しおアプリケヌションの初期状態を定矩し、アプリケヌションコンポヌネントをむンポヌトしおレンダリングし、それらに状態ずセッタヌを小道具ずしお枡したす。



// 
import { useState } from 'react'
// 
import { TodoForm, TodoList } from './components'
// 
import { Container } from 'react-bootstrap'

//  
//    ,    
const initialState = {
  todos: {
    ids: ['1', '2', '3', '4'],
    entities: {
      1: {
        id: '1',
        text: 'Eat',
        completed: true
      },
      2: {
        id: '2',
        text: 'Code',
        completed: true
      },
      3: {
        id: '3',
        text: 'Sleep',
        completed: false
      },
      4: {
        id: '4',
        text: 'Repeat',
        completed: false
      }
    }
  }
}

export default function App() {
  const [state, setState] = useState(initialState)

  const { length } = state.todos.ids

  return (
    <Container style={{ maxWidth: '480px' }} className='text-center'>
      <h1 className='mt-2'>useState</h1>
      <TodoForm setState={setState} />
      {length ? <TodoList state={state} setState={setState} /> : null}
    </Container>
  )
}

      
      





TodoForm.jsでは、リストに新しいタスクを远加するこずを実装しおいたす。



// 
import { useState } from 'react'
//    ID
import { nanoid } from 'nanoid'
// 
import { Container, Form, Button } from 'react-bootstrap'

//   
export const TodoForm = ({ setState }) => {
  const [text, setText] = useState('')

  const updateText = ({ target: { value } }) => {
    setText(value)
  }

  const addTodo = (e) => {
    e.preventDefault()

    const trimmed = text.trim()

    if (trimmed) {
      const id = nanoid(5)

      const newTodo = { id, text, completed: false }

      //  ,     
      setState((state) => ({
        ...state,
        todos: {
          ...state.todos,
          ids: state.todos.ids.concat(id),
          entities: {
            ...state.todos.entities,
            [id]: newTodo
          }
        }
      }))

      setText('')
    }
  }

  return (
    <Container className='mt-4'>
      <h4>Form</h4>
      <Form className='d-flex' onSubmit={addTodo}>
        <Form.Control
          type='text'
          placeholder='Enter text...'
          value={text}
          onChange={updateText}
        />
        <Button variant='primary' type='submit'>
          Add
        </Button>
      </Form>
    </Container>
  )
}

      
      





TodoList.jsでは、アむテムのリストをレンダリングするだけです。



// 
import { TodoListItem } from './TodoListItem'
// 
import { Container, ListGroup } from 'react-bootstrap'

//        ,
//    
//  ,     
export const TodoList = ({ state, setState }) => (
  <Container className='mt-2'>
    <h4>List</h4>
    <ListGroup>
      {state.todos.ids.map((id) => (
        <TodoListItem
          key={id}
          todo={state.todos.entities[id]}
          setState={setState}
        />
      ))}
    </ListGroup>
  </Container>
)

      
      





最埌に、楜しい郚分はTodoListItem.jsで発生したす。ここでは、残りの操䜜タスクの切り替え、曎新、削陀を実装したす。



// 
import { ListGroup, Form, Button } from 'react-bootstrap'

//     
export const TodoListItem = ({ todo, setState }) => {
  const { id, text, completed } = todo

  //  
  const toggleTodo = () => {
    setState((state) => {
      //  
      const { todos } = state

      return {
        ...state,
        todos: {
          ...todos,
          entities: {
            ...todos.entities,
            [id]: {
              ...todos.entities[id],
              completed: !todos.entities[id].completed
            }
          }
        }
      }
    })
  }

  //  
  const updateTodo = ({ target: { value } }) => {
    const trimmed = value.trim()

    if (trimmed) {
      setState((state) => {
        const { todos } = state

        return {
          ...state,
          todos: {
            ...todos,
            entities: {
              ...todos.entities,
              [id]: {
                ...todos.entities[id],
                text: trimmed
              }
            }
          }
        }
      })
    }
  }

  //  
  const deleteTodo = () => {
    setState((state) => {
      const { todos } = state

      const newIds = todos.ids.filter((_id) => _id !== id)

      const newTodos = newIds.reduce((obj, id) => {
        if (todos.entities[id]) return { ...obj, [id]: todos.entities[id] }
        else return obj
      }, {})

      return {
        ...state,
        todos: {
          ...todos,
          ids: newIds,
          entities: newTodos
        }
      }
    })
  }

  //      
  const inputStyles = {
    outline: 'none',
    border: 'none',
    background: 'none',
    textAlign: 'center',
    textDecoration: completed ? 'line-through' : '',
    opacity: completed ? '0.8' : '1'
  }

  return (
    <ListGroup.Item className='d-flex align-items-baseline'>
      <Form.Check
        type='checkbox'
        checked={completed}
        onChange={toggleTodo}
      />
      <Form.Control
        style={inputStyles}
        defaultValue={text}
        onChange={updateTodo}
        disabled={completed}
      />
      <Button variant='danger' onClick={deleteTodo}>
        Delete
      </Button>
    </ListGroup.Item>
  )
}

      
      





components / index.jsで、コンポヌネントを再゚クスポヌトしたす。



export { TodoForm } from './TodoForm'
export { TodoList } from './TodoList'

      
      





scr /index.jsファむルは次のようになりたす。



import React from 'react'
import { render } from 'react-dom'

// 
import 'bootstrap/dist/css/bootstrap.min.css'

// 
import App from './use-state/App'

const root$ = document.getElementById('root')
render(<App />, root$)

      
      





状態管理ぞのこのアプロヌチの䞻な問題



  • 州の地域的な性質により、各ネスティングレベルで州および/たたはセッタヌを転送する必芁性
  • アプリケヌションの状態を曎新するためのロゞックは、コンポヌネント党䜓に分散しおおり、コンポヌネント自䜓のロゞックず混合されおいたす。
  • その䞍倉性から生じる状態曎新の耇雑さ
  • 䞀方向のデヌタフロヌ、同じネストレベルにあるが、仮想DOMの異なるサブツリヌにあるコンポヌネント間でデヌタを自由に亀換するこずは䞍可胜



最初の2぀の問題は、useContext/ useReducerの組み合わせで解決できたす。



useContext+ useReducer



Hooks Cheat Sheet



Contextを䜿甚するず、祖先をバむパスしお、子コンポヌネントに盎接倀を枡すこずができたす。useContextフックを䜿甚するず、プロバむダヌにラップされた任意のコンポヌネントのコンテキストから倀を取埗できたす。



コンテキストの䜜成



const TodoContext = createContext()

      
      





子コンポヌネントにステヌトフルコンテキストを提䟛する



<TodoContext.Provider value={state}>
  <App />
</TodoContext.Provider>

      
      





コンポヌネントのコンテキストから状態倀を抜出する



const state = useContext(TodoContext)

      
      





useReducerフックは、レデュヌサヌず初期状態を受け入れたす。珟圚の状態の倀ず、状態の曎新に基づいお操䜜をディスパッチするための関数を返したす。このフックの眲名は次のずおりです。



const [state, dispatch] = useReducer(todoReducer, initialState)

      
      





状態を曎新するためのアルゎリズムは次のようになりたす。コンポヌネントは操䜜をレデュヌサヌに送信し、レデュヌサヌは操䜜のタむプaction.typeず操䜜のオプションのペむロヌドaction.payloadに基づいお、特定の方法で述べおいたす。



useContextずuseReducerの組み合わせにより、useReducerによっお返された状態ずディスパッチャヌをコンテキストプロバむダヌの子孫である任意のコンポヌネントに枡すこずができたす。



トリックの2番目のバヌゞョン甚に「use-reducer」ディレクトリを䜜成したす。プロゞェクト構造



|--use-reducer
  |--modules
    |--components
      |--index.js
      |--TodoForm.js
      |--TodoList.js
      |--TodoListItem.js
    |--todoReducer
      |--actions.js
      |--actionTypes.js
      |--todoReducer.js
    |--todoContext.js
  |--App.js

      
      





ギアボックスから始めたしょう。actionTypes.jsでは、操䜜のタむプ名前、定数を定矩するだけです。



const ADD_TODO = 'ADD_TODO'
const TOGGLE_TODO = 'TOGGLE_TODO'
const UPDATE_TODO = 'UPDATE_TODO'
const DELETE_TODO = 'DELETE_TODO'

export { ADD_TODO, TOGGLE_TODO, UPDATE_TODO, DELETE_TODO }

      
      





操䜜タむプは、操䜜オブゞェクトを䜜成するずきず、switchステヌトメントでcaseレデュヌサヌを遞択するずきの䞡方で䜿甚されるため、別のファむルで定矩されたす。タむプ、操䜜の䜜成者、およびレデュヌサヌを同じファむルに配眮する別のアプロヌチがありたす。このアプロヌチは「ダック」ファむル構造ず呌ばれたす。



Actions.jsは、特定の圢状のオブゞェクトを返す、いわゆるアクションクリ゚ヌタヌを定矩したすレデュヌサヌ甚。



import { ADD_TODO, TOGGLE_TODO, UPDATE_TODO, DELETE_TODO } from './actionTypes'

const createAction = (type, payload) => ({ type, payload })

const addTodo = (newTodo) => createAction(ADD_TODO, newTodo)
const toggleTodo = (todoId) => createAction(TOGGLE_TODO, todoId)
const updateTodo = (payload) => createAction(UPDATE_TODO, payload)
const deleteTodo = (todoId) => createAction(DELETE_TODO, todoId)

export { addTodo, toggleTodo, updateTodo, deleteTodo }

      
      





レデュヌサヌ自䜓はtodoReducer.jsで定矩されおいたす。この堎合も、レデュヌサヌはアプリケヌションの状態ずコンポヌネントからディスパッチされた操䜜を取埗し、操䜜の皮類およびペむロヌドに基づいお特定のアクションを実行し、その結果、状態が曎新されたす。状態の曎新は、setStateの代わりにレデュヌサヌが新しい状態を返すこずを陀いお、以前のバヌゞョンのトリックず同じ方法で実行されたす。



//    ID
import { nanoid } from 'nanoid'
//  
import * as actions from './actionTypes'

export const todoReducer = (state, action) => {
  const { todos } = state

  switch (action.type) {
    case actions.ADD_TODO: {
      const { payload: newTodo } = action

      const id = nanoid(5)

      return {
        ...state,
        todos: {
          ...todos,
          ids: todos.ids.concat(id),
          entities: {
            ...todos.entities,
            [id]: { id, ...newTodo }
          }
        }
      }
    }

    case actions.TOGGLE_TODO: {
      const { payload: id } = action

      return {
        ...state,
        todos: {
          ...todos,
          entities: {
            ...todos.entities,
            [id]: {
              ...todos.entities[id],
              completed: !todos.entities[id].completed
            }
          }
        }
      }
    }

    case actions.UPDATE_TODO: {
      const { payload: id, text } = action

      return {
        ...state,
        todos: {
          ...todos,
          entities: {
            ...todos.entities,
            [id]: {
              ...todos.entities[id],
              text
            }
          }
        }
      }
    }

    case actions.DELETE_TODO: {
      const { payload: id } = action

      const newIds = todos.ids.filter((_id) => _id !== id)

      const newTodos = newIds.reduce((obj, id) => {
        if (todos.entities[id]) return { ...obj, [id]: todos.entities[id] }
        else return obj
      }, {})

      return {
        ...state,
        todos: {
          ...todos,
          ids: newIds,
          entities: newTodos
        }
      }
    }
    //   (     case)      
    default:
      return state
  }
}

      
      





TodoContext.jsは、アプリケヌションの初期状態を定矩し、状態倀を䜿甚しおコンテキストプロバむダヌを䜜成し、useReducerからディスパッチャヌを゚クスポヌトしたす。



// react
import { createContext, useReducer, useContext } from 'react'
// 
import { todoReducer } from './todoReducer/todoReducer'

//  
const TodoContext = createContext()

//  
const initialState = {
  todos: {
    ids: ['1', '2', '3', '4'],
    entities: {
      1: {
        id: '1',
        text: 'Eat',
        completed: true
      },
      2: {
        id: '2',
        text: 'Code',
        completed: true
      },
      3: {
        id: '3',
        text: 'Sleep',
        completed: false
      },
      4: {
        id: '4',
        text: 'Repeat',
        completed: false
      }
    }
  }
}

// 
export const TodoProvider = ({ children }) => {
  const [state, dispatch] = useReducer(todoReducer, initialState)

  return (
    <TodoContext.Provider value={{ state, dispatch }}>
      {children}
    </TodoContext.Provider>
  )
}

//      
export const useTodoContext = () => useContext(TodoContext)

      
      





この堎合、src /index.jsは次のようになりたす。



// React, ReactDOM  

import { TodoProvider } from './use-reducer/modules/TodoContext'

import App from './use-reducer/App'

const root$ = document.getElementById('root')
render(
  <TodoProvider>
    <App />
  </TodoProvider>,
  root$
)

      
      





これで、コンポヌネントのネストの各レベルで状態ず関数を枡しお曎新する必芁がなくなりたした。コンポヌネントは、useTodoContextを䜿甚しお状態ずディスパッチャヌを取埗したす。次に䟋を瀺したす。



import { useTodoContext } from '../TodoContext'

//  
const { state, dispatch } = useTodoContext()

      
      





オペレヌションは、ディスパッチを䜿甚しおレデュヌサヌにディスパッチされたす。ディスパッチには、オペレヌションの䜜成者が枡され、ペむロヌドを枡すこずができたす。



import * as actions from '../todoReducer/actions'

//  
dispatch(actions.addTodo(newTodo))

      
      





コンポヌネントコヌド
App.js:



// components
import { TodoForm, TodoList } from './modules/components'
// styles
import { Container } from 'react-bootstrap'
// context
import { useTodoContext } from './modules/TodoContext'

export default function App() {
  const { state } = useTodoContext()

  const { length } = state.todos.ids

  return (
    <Container style={{ maxWidth: '480px' }} className='text-center'>
      <h1 className='mt-2'>useReducer</h1>
      <TodoForm />
      {length ? <TodoList /> : null}
    </Container>
  )
}

      
      





TodoForm.js:



// react
import { useState } from 'react'
// styles
import { Container, Form, Button } from 'react-bootstrap'
// context
import { useTodoContext } from '../TodoContext'
// actions
import * as actions from '../todoReducer/actions'

export const TodoForm = () => {
  const { dispatch } = useTodoContext()
  const [text, setText] = useState('')

  const updateText = ({ target: { value } }) => {
    setText(value)
  }

  const handleAddTodo = (e) => {
    e.preventDefault()

    const trimmed = text.trim()

    if (trimmed) {
      const newTodo = { text, completed: false }

      dispatch(actions.addTodo(newTodo))

      setText('')
    }
  }

  return (
    <Container className='mt-4'>
      <h4>Form</h4>
      <Form className='d-flex' onSubmit={handleAddTodo}>
        <Form.Control
          type='text'
          placeholder='Enter text...'
          value={text}
          onChange={updateText}
        />
        <Button variant='primary' type='submit'>
          Add
        </Button>
      </Form>
    </Container>
  )
}

      
      





TodoList.js:



// components
import { TodoListItem } from './TodoListItem'
// styles
import { Container, ListGroup } from 'react-bootstrap'
// context
import { useTodoContext } from '../TodoContext'

export const TodoList = () => {
  const {
    state: { todos }
  } = useTodoContext()

  return (
    <Container className='mt-2'>
      <h4>List</h4>
      <ListGroup>
        {todos.ids.map((id) => (
          <TodoListItem key={id} todo={todos.entities[id]} />
        ))}
      </ListGroup>
    </Container>
  )
}

      
      





TodoListItem.js:



// styles
import { ListGroup, Form, Button } from 'react-bootstrap'
// context
import { useTodoContext } from '../TodoContext'
// actions
import * as actions from '../todoReducer/actions'

export const TodoListItem = ({ todo }) => {
  const { dispatch } = useTodoContext()

  const { id, text, completed } = todo

  const handleUpdateTodo = ({ target: { value } }) => {
    const trimmed = value.trim()

    if (trimmed) {
      dispatch(actions.updateTodo({ id, trimmed }))
    }
  }

  const inputStyles = {
    outline: 'none',
    border: 'none',
    background: 'none',
    textAlign: 'center',
    textDecoration: completed ? 'line-through' : '',
    opacity: completed ? '0.8' : '1'
  }

  return (
    <ListGroup.Item className='d-flex align-items-baseline'>
      <Form.Check
        type='checkbox'
        checked={completed}
        onChange={() => dispatch(actions.toggleTodo(id))}
      />
      <Form.Control
        style={inputStyles}
        defaultValue={text}
        onChange={handleUpdateTodo}
        disabled={completed}
      />
      <Button variant='danger' onClick={() => dispatch(actions.deleteTodo(id))}>
        Delete
      </Button>
    </ListGroup.Item>
  )
}

      
      







したがっお、状態を管理するためのツヌルずしおuseStateを䜿甚するこずに関連する最初の2぀の問題を解決したした。実際、興味深いラむブラリの助けを借りお、3番目の問題である状態の曎新の耇雑さを解決できたす。 immerを䜿甚するず、䞍倉の倀を安党に倉曎できたすはい、それがどのように聞こえるかはわかりたす。レデュヌサヌを「produce」関数でラップするだけです。「todoReducer / todoProducer.js」ずいうファむルを䜜成したしょう。



// ,  immer
import produce from 'immer'
import { nanoid } from 'nanoid'
//  
import * as actions from './actionTypes'

//   ""  
//     draft -   
export const todoProducer = produce((draft, action) => {
  const {
    todos: { ids, entities }
  } = draft

  switch (action.type) {
    case actions.ADD_TODO: {
      const { payload: newTodo } = action

      const id = nanoid(5)

      ids.push(id)
      entities[id] = { id, ...newTodo }
      break
    }
    case actions.TOGGLE_TODO: {
      const { payload: id } = action

      entities[id].completed = !entities[id].completed
      break
    }
    case actions.UPDATE_TODO: {
      const { payload: id, text } = action

      entities[id].text = text
      break
    }
    case actions.DELETE_TODO: {
      const { payload: id } = action

      ids.splice(ids.indexOf(id), 1)
      delete entities[id]
      break
    }
    default:
      return draft
  }
})

      
      





むマヌが課す䞻な制限は、状態を盎接倉曎するか、䞍倉に曎新された状態を返す必芁があるこずです。䞡方を同時に行うこずはできたせん。



todoContext.jsに倉曎を加えたす。



// import { todoReducer } from './todoReducer/todoReducer'
import { todoProducer } from './todoReducer/todoProducer'

//  
// const [state, dispatch] = useReducer(todoReducer, initialState)
const [state, dispatch] = useReducer(todoProducer, initialState)

      
      





すべおが以前ず同じように機胜したすが、レデュヌサヌコヌドの読み取りず解析が簡単になりたした。



先に進みたす。



Reduxツヌルキット



Reduxツヌルキットガむド



Reduxツヌルキットは、Reduxの操䜜を簡単にするツヌルのコレクションです。Redux自䜓は、useContext+ useReducerで実装したものず非垞によく䌌おいたす。



  • アプリケヌション党䜓の状態は1぀のストアにありたす
  • 子コンポヌネントはreact-reduxからプロバむダヌにラップされ、ストアは「ストア」プロップずしお枡されたす
  • 状態の各郚分のレデュヌサヌは、combineReducersを䜿甚しお単䞀のルヌトレデュヌサヌに結合され、ストアの䜜成時にcreateStoreに枡されたす。
  • コンポヌネントは、connect+ mapStateToProps、mapDispatchToPropsなどを䜿甚しおストアに接続されたす。


基本的な操䜜を実装するために、ReduxToolkitの次のナヌティリティを䜿甚したす。



  • configureStore-ストアを䜜成および構成するため
  • createSlice-状態の䞀郚を䜜成したす
  • createEntityAdapter-゚ンティティアダプタを䜜成したす


少し埌で、次のナヌティリティを䜿甚しおタスクリストの機胜を拡匵したす。



  • createSelector-セレクタヌを䜜成するため
  • createAsyncThunk-サンクを䜜成したす


たた、コンポヌネントでは、react-reduxから次のフックを䜿甚したす "useDispatch"-ディスパッチャヌぞのアクセスを取埗し、 "useSelector"-セレクタヌぞのアクセスを取埗したす。



ツむストの3番目のバヌゞョン甚のディレクトリ「redux-toolkit」を䜜成したす。Reduxツヌルキットをむンストヌルしたす。



yarn add @reduxjs/toolkit
# 
npm i @reduxjs/toolkit

      
      





プロゞェクト構造



|--redux-toolkit
  |--modules
    |--components
      |--index.js
      |--TodoForm.js
      |--TodoList.js
      |--TodoListItem.js
  |--slices
    |--todosSlice.js
  |--App.js
  |--store.js

      
      





リポゞトリから始めたしょう。store.js



//    
import { configureStore } from '@reduxjs/toolkit'
// 
import todosReducer from './modules/slices/todosSlice'

//  
const preloadedState = {
  todos: {
    ids: ['1', '2', '3', '4'],
    entities: {
      1: {
        id: '1',
        text: 'Eat',
        completed: true
      },
      2: {
        id: '2',
        text: 'Code',
        completed: true
      },
      3: {
        id: '3',
        text: 'Sleep',
        completed: false
      },
      4: {
        id: '4',
        text: 'Repeat',
        completed: false
      }
    }
  }
}

// 
const store = configureStore({
  reducer: {
    todos: todosReducer
  },
  preloadedState
})

export default store

      
      





この堎合、src /index.jsは次のようになりたす。



// React, ReactDOM & 

// 
import { Provider } from 'react-redux'

//  
import App from './redux-toolkit/App'
// 
import store from './redux-toolkit/store'

const root$ = document.getElementById('root')
render(
  <Provider store={store}>
    <App />
  </Provider>,
  root$
)

      
      





ギアボックスに枡したす。スラむス/todosSlice.js



//        
import {
  createSlice,
  createEntityAdapter
} from '@reduxjs/toolkit'

//  
const todosAdapter = createEntityAdapter()

//   
//  { ids: [], entities: {} }
const initialState = todosAdapter.getInitialState()

//   
const todosSlice = createSlice({
  //  ,        
  name: 'todos',
  //  
  initialState,
  // 
  reducers: {
    //        { type: 'todos/addTodo', payload: newTodo }
    addTodo: todosAdapter.addOne,
    // Redux Toolkit  immer   
    toggleTodo(state, action) {
      const { payload: id } = action
      const todo = state.entities[id]
      todo.completed = !todo.completed
    },
    updateTodo(state, action) {
      const { id, text } = action.payload
      const todo = state.entities[id]
      todo.text = text
    },
    deleteTodo: todosAdapter.removeOne
  }
})

//      entities   
export const { selectAll: selectAllTodos } = todosAdapter.getSelectors(
  (state) => state.todos
)

//   
export const {
  addTodo,
  toggleTodo,
  updateTodo,
  deleteTodo
} = todosSlice.actions

//  
export default todosSlice.reducer

      
      





コンポヌネントでは、useDispatchを䜿甚しおディスパッチャヌにアクセスし、todosSlice.jsからむンポヌトされたアクティビティクリ゚ヌタヌを䜿甚しお特定の操䜜をディスパッチしたす。



import { useDispatch } from 'react-redux'
import { addTodo } from '../slices/todosSlice'

//  
const dispatch = useDispatch()

dispatch(addTodo(newTodo))

      
      





tudushkaの機胜を少し拡匵しおみたしょう。぀たり、タスクをフィルタリングする機胜、すべおのタスクを完了しお完了したタスクを削陀するボタン、およびいく぀かの有甚な統蚈を远加したす。サヌバヌからタスクのリストを取埗するこずも実装したしょう。



サヌバヌから始めたしょう。 JSONサヌバヌ



を「停のAPI」ずしお䜿甚し たす。ここだ 、それを操䜜するためのチヌトシヌト。json-serverを同時にむンストヌルしたす-2 ぀以䞊のコマンドを実行するためのナヌティリティ



yarn add json-server concurrently
# 
npm i json-server concurrently

      
      





package.jsonの「scripts」セクションに倉曎を加えたす。



"server": "concurrently \"json-server -w db.json -p 5000 -d 1000\" \"yarn start\""

      
      





  • -w-「db.json」ファむルぞの倉曎を監芖するこずを意味したす
  • -p-ポヌトを意味したす。デフォルトでは、アプリケヌションからの芁求はポヌト3000に送信されたす。
  • -d-サヌバヌからの応答を遅らせる


プロゞェクトのルヌトディレクトリにファむル「db.json」を䜜成したす状態管理。



{
  "todos": [
    {
      "id": "1",
      "text": "Eat",
      "completed": true,
      "visible": true
    },
    {
      "id": "2",
      "text": "Code",
      "completed": true,
      "visible": true
    },
    {
      "id": "3",
      "text": "Sleep",
      "completed": false,
      "visible": true
    },
    {
      "id": "4",
      "text": "Repeat",
      "completed": false,
      "visible": true
    }
  ]
}

      
      





デフォルトでは、アプリケヌションからのすべおの芁求はポヌト3000開発サヌバヌが実行されおいるポヌトに送信されたす。リク゚ストをポヌト5000json-serverが実行されるポヌトに送信するには、リク゚ストをプロキシする必芁がありたす。package.jsonに次の行を远加したす。



"proxy": "http://localhost:5000"

      
      





「yarnserver」コマンドを䜿甚しおサヌバヌを起動したす。



状態の別の郚分を䜜成したす。スラむス/filterSlice.js



import { createSlice } from '@reduxjs/toolkit'

// 
export const Filters = {
  All: 'all',
  Active: 'active',
  Completed: 'completed'
}

//   -   
const initialState = {
  status: Filters.All
}

//  
const filterSlice = createSlice({
  name: 'filter',
  initialState,
  reducers: {
    setFilter(state, action) {
      state.status = action.payload
    }
  }
})

export const { setFilter } = filterSlice.actions

export default filterSlice.reducer

      
      





store.jsに倉曎を加えたす。



//     preloadedState
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './modules/slices/todosSlice'
import filterReducer from './modules/slices/filterSlice'

const store = configureStore({
  reducer: {
    todos: todosReducer,
    filter: filterReducer
  }
})

export default store

      
      





todosSlice.jsに倉曎を加えたす。



import {
  createSlice,
  createEntityAdapter,
  //    
  createSelector,
  //    
  createAsyncThunk
} from '@reduxjs/toolkit'
//    HTTP-
import axios from 'axios'

// 
import { Filters } from './filterSlice'

const todosAdapter = createEntityAdapter()

const initialState = todosAdapter.getInitialState({
  //      
  status: 'idle'
})

//  
const SERVER_URL = 'http://localhost:5000/todos'
// 
export const fetchTodos = createAsyncThunk('todos/fetchTodos', async () => {
  try {
    const response = await axios(SERVER_URL)
    return response.data
  } catch (err) {
    console.error(err.toJSON())
  }
})

const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    addTodo: todosAdapter.addOne,
    toggleTodo(state, action) {
      const { payload: id } = action
      const todo = state.entities[id]
      todo.completed = !todo.completed
    },
    updateTodo(state, action) {
      const { id, text } = action.payload
      const todo = state.entities[id]
      todo.text = text
    },
    deleteTodo: todosAdapter.removeOne,
    //      
    completeAllTodos(state) {
      Object.values(state.entities).forEach((todo) => {
        todo.completed = true
      })
    },
    //      
    clearCompletedTodos(state) {
      const completedIds = Object.values(state.entities)
        .filter((todo) => todo.completed)
        .map((todo) => todo.id)
      todosAdapter.removeMany(state, completedIds)
    }
  },
  //  
  extraReducers: (builder) => {
    builder
      //       
      //     loading
      //       App.js
      .addCase(fetchTodos.pending, (state) => {
        state.status = 'loading'
      })
      //     
      //    
      //    
      .addCase(fetchTodos.fulfilled, (state, action) => {
        todosAdapter.setAll(state, action.payload)
        state.status = 'idle'
      })
  }
})

export const { selectAll: selectAllTodos } = todosAdapter.getSelectors(
  (state) => state.todos
)

//         
export const selectFilteredTodos = createSelector(
  selectAllTodos,
  (state) => state.filter,
  (todos, filter) => {
    const { status } = filter
    if (status === Filters.All) return todos
    return status === Filters.Active
      ? todos.filter((todo) => !todo.completed)
      : todos.filter((todo) => todo.completed)
  }
)

export const {
  addTodo,
  toggleTodo,
  updateTodo,
  deleteTodo,
  completeAllTodos,
  clearCompletedTodos
} = todosSlice.actions

export default todosSlice.reducer

      
      





src /index.jsに倉曎を加えたす。



//    "App"
import { fetchTodos } from './redux-toolkit/modules/slices/todosSlice'

store.dispatch(fetchTodos())

      
      





App.jsは次のようになりたす。



//     
import { useSelector } from 'react-redux'
//   - 
import Loader from 'react-loader-spinner'
// 
import {
  TodoForm,
  TodoList,
  TodoFilters,
  TodoControls,
  TodoStats
} from './modules/components'
// 
import { Container } from 'react-bootstrap'
//     entitites   
import { selectAllTodos } from './modules/slices/todosSlice'

export default function App() {
  //    
  const { length } = useSelector(selectAllTodos)
  //   
  const loadingStatus = useSelector((state) => state.todos.status)

  //    
  const loaderStyles = {
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)'
  }

  if (loadingStatus === 'loading')
    return (
      <Loader
        type='Oval'
        color='#00bfff'
        height={80}
        width={80}
        style={loaderStyles}
      />
    )

  return (
    <Container style={{ maxWidth: '480px' }} className='text-center'>
      <h1 className='mt-2'>Redux Toolkit</h1>
      <TodoForm />
      {length ? (
        <>
          <TodoStats />
          <TodoFilters />
          <TodoList />
          <TodoControls />
        </>
      ) : null}
    </Container>
  )
}

      
      





その他のコンポヌネントコヌド
TodoControls.js:



// redux
import { useDispatch } from 'react-redux'
// styles
import { Container, ButtonGroup, Button } from 'react-bootstrap'
// action creators
import { completeAllTodos, clearCompletedTodos } from '../slices/todosSlice'

export const TodoControls = () => {
  const dispatch = useDispatch()

  return (
    <Container className='mt-2'>
      <h4>Controls</h4>
      <ButtonGroup>
        <Button
          variant='outline-secondary'
          onClick={() => dispatch(completeAllTodos())}
        >
          Complete all
        </Button>
        <Button
          variant='outline-secondary'
          onClick={() => dispatch(clearCompletedTodos())}
        >
          Clear completed
        </Button>
      </ButtonGroup>
    </Container>
  )
}

      
      





TodoFilters.js:



// redux
import { useDispatch, useSelector } from 'react-redux'
// styles
import { Container, Form } from 'react-bootstrap'
// filters & action creator
import { Filters, setFilter } from '../slices/filterSlice'

export const TodoFilters = () => {
  const dispatch = useDispatch()
  const { status } = useSelector((state) => state.filter)

  const changeFilter = (filter) => {
    dispatch(setFilter(filter))
  }

  return (
    <Container className='mt-2'>
      <h4>Filters</h4>
      {Object.keys(Filters).map((key) => {
        const value = Filters[key]
        const checked = value === status

        return (
          <Form.Check
            key={value}
            inline
            label={value.toUpperCase()}
            type='radio'
            name='filter'
            onChange={() => changeFilter(value)}
            checked={checked}
          />
        )
      })}
    </Container>
  )
}

      
      





TodoForm.js:



// react
import { useState } from 'react'
// redux
import { useDispatch } from 'react-redux'
// libs
import { nanoid } from 'nanoid'
// styles
import { Container, Form, Button } from 'react-bootstrap'
// action creator
import { addTodo } from '../slices/todosSlice'

export const TodoForm = () => {
  const dispatch = useDispatch()
  const [text, setText] = useState('')

  const updateText = ({ target: { value } }) => {
    setText(value)
  }

  const handleAddTodo = (e) => {
    e.preventDefault()

    const trimmed = text.trim()

    if (trimmed) {
      const newTodo = { id: nanoid(5), text, completed: false }

      dispatch(addTodo(newTodo))

      setText('')
    }
  }

  return (
    <Container className='mt-4'>
      <h4>Form</h4>
      <Form className='d-flex' onSubmit={handleAddTodo}>
        <Form.Control
          type='text'
          placeholder='Enter text...'
          value={text}
          onChange={updateText}
        />
        <Button variant='primary' type='submit'>
          Add
        </Button>
      </Form>
    </Container>
  )
}

      
      





TodoList.js:



// redux
import { useSelector } from 'react-redux'
// component
import { TodoListItem } from './TodoListItem'
// styles
import { Container, ListGroup } from 'react-bootstrap'
// selector
import { selectFilteredTodos } from '../slices/todosSlice'

export const TodoList = () => {
  const filteredTodos = useSelector(selectFilteredTodos)

  return (
    <Container className='mt-2'>
      <h4>List</h4>
      <ListGroup>
        {filteredTodos.map((todo) => (
          <TodoListItem key={todo.id} todo={todo} />
        ))}
      </ListGroup>
    </Container>
  )
}

      
      





TodoListItem.js:



// redux
import { useDispatch } from 'react-redux'
// styles
import { ListGroup, Form, Button } from 'react-bootstrap'
// action creators
import { toggleTodo, updateTodo, deleteTodo } from '../slices/todosSlice'

export const TodoListItem = ({ todo }) => {
  const dispatch = useDispatch()

  const { id, text, completed } = todo

  const handleUpdateTodo = ({ target: { value } }) => {
    const trimmed = value.trim()

    if (trimmed) {
      dispatch(updateTodo({ id, trimmed }))
    }
  }

  const inputStyles = {
    outline: 'none',
    border: 'none',
    background: 'none',
    textAlign: 'center',
    textDecoration: completed ? 'line-through' : '',
    opacity: completed ? '0.8' : '1'
  }

  return (
    <ListGroup.Item className='d-flex align-items-baseline'>
      <Form.Check
        type='checkbox'
        checked={completed}
        onChange={() => dispatch(toggleTodo(id))}
      />
      <Form.Control
        style={inputStyles}
        defaultValue={text}
        onChange={handleUpdateTodo}
        disabled={completed}
      />
      <Button variant='danger' onClick={() => dispatch(deleteTodo(id))}>
        Delete
      </Button>
    </ListGroup.Item>
  )
}

      
      





TodoStats.js:



// react
import { useState, useEffect } from 'react'
// redux
import { useSelector } from 'react-redux'
// styles
import { Container, ListGroup } from 'react-bootstrap'
// selector
import { selectAllTodos } from '../slices/todosSlice'

export const TodoStats = () => {
  const allTodos = useSelector(selectAllTodos)

  const [stats, setStats] = useState({
    total: 0,
    active: 0,
    completed: 0,
    percent: 0
  })

  useEffect(() => {
    if (allTodos.length) {
      const total = allTodos.length
      const completed = allTodos.filter((todo) => todo.completed).length
      const active = total - completed
      const percent = total === 0 ? 0 : Math.round((active / total) * 100) + '%'

      setStats({
        total,
        active,
        completed,
        percent
      })
    }
  }, [allTodos])

  return (
    <Container className='mt-2'>
      <h4>Stats</h4>
      <ListGroup horizontal>
        {Object.entries(stats).map(([[first, ...rest], count], index) => (
          <ListGroup.Item key={index}>
            {first.toUpperCase() + rest.join('')}: {count}
          </ListGroup.Item>
        ))}
      </ListGroup>
    </Container>
  )
}

      
      







ご芧のずおり、Redux Toolkitの登堎により、Reduxがアプリケヌションの状態を管理するためにReduxを䜿甚するこずは、useContext+ useReducerの組み合わせを䜿甚するよりも簡単になりたした信じられないが本圓です。管理。ただし、Reduxは䟝然ずしお倧芏暡で耇雑なステヌトフルアプリケヌション向けに蚭蚈されおいたす。useContext/ useReducer以倖に、䞭小芏暡のアプリケヌションの状態を管理するための代替手段はありたすか。答えはむ゚スです。これは リコむルです。



反動



Recoilガむド



Recoilは、Reactアプリケヌションの状態を管理するための新しいツヌルです。 newずはどういう意味ですかこれは、䞀郚のAPIがただ開発䞭であり、将来倉曎される可胜性があるこずを意味したす。ただし、tudushkaを䜜成するために䜿甚する機䌚は安定しおいたす。



アトムずセレクタヌはリコむルの䞭心です。アトムは状態の䞀郚であり、セレクタヌは掟生状態の䞀郚です。アトムは「atom」関数を䜿甚しお䜜成され、セレクタヌは「selector」関数を䜿甚しお䜜成されたす。アトムずセレクタヌから倀を取埗するには、useRecoilState読み取りず曞き蟌み、useRecoilValue読み取り専甚、useSetRecoilState曞き蟌み専甚フックなどを䜿甚したす。Recoil状態を䜿甚するコンポヌネントは、RecoilRootでラップする必芁がありたす。 。RecoilはuseStateずReduxの䞭間にあるように感じたす。



最新のtudushkaの「recoil」ディレクトリを䜜成し、Recoilをむンストヌルしたす。



yarn add recoil
# 
npm i recoil

      
      





プロゞェクト構造



|--recoil
  |--modules
    |--atoms
      |--filterAtom.js
      |--todosAtom.js
    |--components
      |--index.js
      |--TodoControls.js
      |--TodoFilters.js
      |--TodoForm.js
      |--TodoList.js
      |--TodoListItem.js
      |--TodoStats.js
  |--App.js

      
      





タスクリストアトムは次のようになりたす。



// todosAtom.js
//      
import { atom, selector } from 'recoil'
//    HTTP-
import axios from 'axios'

//  
const SERVER_URL = 'http://localhost:5000/todos'

//      
export const todosState = atom({
  key: 'todosState',
  default: selector({
    key: 'todosState/default',
    get: async () => {
      try {
        const response = await axios(SERVER_URL)
        return response.data
      } catch (err) {
        console.log(err.toJSON())
      }
    }
  })
})

      
      





Recoilの興味深い点の1぀は、アトムずセレクタヌを䜜成するずきに同期ロゞックず非同期ロゞックを混圚させるこずができるこずです。これは、React Suspenseを䜿甚しお、デヌタを受信する前にフォヌルバックコンテンツをレンダリングできるように蚭蚈されおいたす。たた、ヒュヌズErrorBoundaryを䜿甚しお、非同期の方法を含め、アトムずセレクタヌの䜜成時に発生する゚ラヌをキャッチする機胜もありたす。



この堎合、src /index.jsは次のようになりたす。



import React, { Component, Suspense } from 'react'
import { render } from 'react-dom'
// recoil
import { RecoilRoot } from 'recoil'

//  
import Loader from 'react-loader-spinner'

import App from './recoil/App'

//     React
class ErrorBoundary extends Component {
  constructor(props) {
    super(props)
    this.state = { error: null, errorInfo: null }
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
  }

  render() {
    if (this.state.errorInfo) {
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      )
    }
    return this.props.children
  }
}

const loaderStyles = {
  position: 'absolute',
  top: '50%',
  left: '50%',
  transform: 'translate(-50%, -50%)'
}

const root$ = document.getElementById('root')
//        Suspense,   ErrorBoundary
render(
  <RecoilRoot>
    <Suspense
      fallback={
        <Loader
          type='Oval'
          color='#00bfff'
          height={80}
          width={80}
          style={loaderStyles}
        />
      }
    >
      <ErrorBoundary>
        <App />
      </ErrorBoundary>
    </Suspense>
  </RecoilRoot>,
  root$
)

      
      





フィルタアトムは次のようになりたす。



// filterAtom.js
// recoil
import { atom, selector } from 'recoil'
// 
import { todosState } from './todosAtom'

export const Filters = {
  All: 'all',
  Active: 'active',
  Completed: 'completed'
}

export const todoListFilterState = atom({
  key: 'todoListFilterState',
  default: Filters.All
})

//     :      
export const filteredTodosState = selector({
  key: 'filteredTodosState',
  get: ({ get }) => {
    const filter = get(todoListFilterState)
    const todos = get(todosState)

    if (filter === Filters.All) return todos

    return filter === Filters.Completed
      ? todos.filter((todo) => todo.completed)
      : todos.filter((todo) => !todo.completed)
  }
})

      
      





コンポヌネントは、䞊蚘のフックを䜿甚しおアトムずセレクタヌから倀を抜出したす。たずえば、「TodoListItem」コンポヌネントのコヌドは次のようになりたす。



// 
import { useRecoilState } from 'recoil'
// 
import { ListGroup, Form, Button } from 'react-bootstrap'
// 
import { todosState } from '../atoms/todosAtom'

export const TodoListItem = ({ todo }) => {
  //   -   useState()   Recoil
  const [todos, setTodos] = useRecoilState(todosState)
  const { id, text, completed } = todo

  const toggleTodo = () => {
    const newTodos = todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )

    setTodos(newTodos)
  }

  const updateTodo = ({ target: { value } }) => {
    const trimmed = value.trim()

    if (!trimmed) return

    const newTodos = todos.map((todo) =>
      todo.id === id ? { ...todo, text: value } : todo
    )

    setTodos(newTodos)
  }

  const deleteTodo = () => {
    const newTodos = todos.filter((todo) => todo.id !== id)

    setTodos(newTodos)
  }

  const inputStyles = {
    outline: 'none',
    border: 'none',
    background: 'none',
    textAlign: 'center',
    textDecoration: completed ? 'line-through' : '',
    opacity: completed ? '0.8' : '1'
  }

  return (
    <ListGroup.Item className='d-flex align-items-baseline'>
      <Form.Check type='checkbox' checked={completed} onChange={toggleTodo} />
      <Form.Control
        style={inputStyles}
        defaultValue={text}
        onChange={updateTodo}
        disabled={completed}
      />
      <Button variant='danger' onClick={deleteTodo}>
        Delete
      </Button>
    </ListGroup.Item>
  )
}

      
      





その他のコンポヌネントコヌド
TodoControls.js:



// recoil
import { useRecoilState } from 'recoil'
// styles
import { Container, ButtonGroup, Button } from 'react-bootstrap'
// atom
import { todosState } from '../atoms/todosAtom'

export const TodoControls = () => {
  const [todos, setTodos] = useRecoilState(todosState)

  const completeAllTodos = () => {
    const newTodos = todos.map((todo) => (todo.completed = true))

    setTodos(newTodos)
  }

  const clearCompletedTodos = () => {
    const newTodos = todos.filter((todo) => !todo.completed)

    setTodos(newTodos)
  }

  return (
    <Container className='mt-2'>
      <h4>Controls</h4>
      <ButtonGroup>
        <Button variant='outline-secondary' onClick={completeAllTodos}>
          Complete all
        </Button>
        <Button variant='outline-secondary' onClick={clearCompletedTodos}>
          Clear completed
        </Button>
      </ButtonGroup>
    </Container>
  )
}

      
      





TodoFilters.js:



// recoil
import { useRecoilState } from 'recoil'
// styles
import { Container, Form } from 'react-bootstrap'
// filters & atom
import { Filters, todoListFilterState } from '../atoms/filterAtom'

export const TodoFilters = () => {
  const [filter, setFilter] = useRecoilState(todoListFilterState)

  return (
    <Container className='mt-2'>
      <h4>Filters</h4>
      {Object.keys(Filters).map((key) => {
        const value = Filters[key]
        const checked = value === filter

        return (
          <Form.Check
            key={value}
            inline
            label={value.toUpperCase()}
            type='radio'
            name='filter'
            onChange={() => setFilter(value)}
            checked={checked}
          />
        )
      })}
    </Container>
  )
}

      
      





TodoForm.js:



// react
import { useState } from 'react'
// recoil
import { useSetRecoilState } from 'recoil'
// libs
import { nanoid } from 'nanoid'
// styles
import { Container, Form, Button } from 'react-bootstrap'
// atom
import { todosState } from '../atoms/todosAtom'

export const TodoForm = () => {
  const [text, setText] = useState('')
  const setTodos = useSetRecoilState(todosState)

  const updateText = ({ target: { value } }) => {
    setText(value)
  }

  const addTodo = (e) => {
    e.preventDefault()

    const trimmed = text.trim()

    if (trimmed) {
      const newTodo = { id: nanoid(5), text, completed: false }

      setTodos((oldTodos) => oldTodos.concat(newTodo))

      setText('')
    }
  }

  return (
    <Container className='mt-4'>
      <h4>Form</h4>
      <Form className='d-flex' onSubmit={addTodo}>
        <Form.Control
          type='text'
          placeholder='Enter text...'
          value={text}
          onChange={updateText}
        />
        <Button variant='primary' type='submit'>
          Add
        </Button>
      </Form>
    </Container>
  )
}

      
      





TodoList.js:



// recoil
import { useRecoilValue } from 'recoil'
// components
import { TodoListItem } from './TodoListItem'
// styles
import { Container, ListGroup } from 'react-bootstrap'
// atom
import { filteredTodosState } from '../atoms/filterAtom'

export const TodoList = () => {
  const filteredTodos = useRecoilValue(filteredTodosState)

  return (
    <Container className='mt-2'>
      <h4>List</h4>
      <ListGroup>
        {filteredTodos.map((todo) => (
          <TodoListItem key={todo.id} todo={todo} />
        ))}
      </ListGroup>
    </Container>
  )
}

      
      





TodoStats.js:



// react
import { useState, useEffect } from 'react'
// recoil
import { useRecoilValue } from 'recoil'
// styles
import { Container, ListGroup } from 'react-bootstrap'
// atom
import { todosState } from '../atoms/todosAtom'

export const TodoStats = () => {
  const todos = useRecoilValue(todosState)

  const [stats, setStats] = useState({
    total: 0,
    active: 0,
    completed: 0,
    percent: 0
  })

  useEffect(() => {
    if (todos.length) {
      const total = todos.length
      const completed = todos.filter((todo) => todo.completed).length
      const active = total - completed
      const percent = total === 0 ? 0 : Math.round((active / total) * 100) + '%'

      setStats({
        total,
        active,
        completed,
        percent
      })
    }
  }, [todos])

  return (
    <Container className='mt-2'>
      <h4>Stats</h4>
      <ListGroup horizontal>
        {Object.entries(stats).map(([[first, ...rest], count], index) => (
          <ListGroup.Item key={index}>
            {first.toUpperCase() + rest.join('')}: {count}
          </ListGroup.Item>
        ))}
      </ListGroup>
    </Container>
  )
}

      
      







結論



したがっお、あなたず私は、状態を管理するための4぀の異なるアプロヌチを䜿甚しおタスクのリストを実装したした。これらすべおからどのような結論を匕き出すこずができたすか



私は私の意芋を衚明したす、それは究極の真実であるずは䞻匵したせん。もちろん、適切な状態管理ツヌルの遞択は、アプリケヌションのタスクによっお異なりたす。



  • ロヌカル状態1぀たたは2぀のコンポヌネントの状態。2぀が密接に関連しおいるず仮定を管理するには、useStateを䜿甚したす。
  • RecoilたたはuseContext/ useReducerを䜿甚しお、分散状態2぀以䞊の自埋コンポヌネントの状態たたは䞭小芏暡のアプリケヌションの状態を管理したす。
  • 深くネストされたコンポヌネントに倀を枡す必芁がある堎合は、useContextで問題ありたせんuseContext自䜓は状態を管理するためのツヌルではありたせん
  • 最埌に、グロヌバル状態すべおたたはほずんどのコンポヌネントの状態たたは耇雑なアプリケヌションの状態を管理するには、ReduxToolkitを䜿甚したす


良いこずをたくさん聞いたMobXは、ただ手に入れおいたせん。



ご枅聎ありがずうございたした。良い䞀日を。



All Articles