Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

day2の課題「非同期Counterの追加実装」の一例 #2

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
276 changes: 259 additions & 17 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Expand Up @@ -9,8 +9,10 @@
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^1.8.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-redux": "^8.0.2"
},
"devDependencies": {
"@types/react": "^18.0.15",
Expand All @@ -19,4 +21,4 @@
"typescript": "^4.6.4",
"vite": "^3.0.0"
}
}
}
33 changes: 5 additions & 28 deletions src/App.tsx
@@ -1,34 +1,11 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
import { Counter } from './features/counter/Counter';

function App() {
const [count, setCount] = useState(0)

return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
<div>
<Counter />
</div>
)
);
}

export default App
export default App;
7 changes: 7 additions & 0 deletions src/app/hooks.ts
@@ -0,0 +1,7 @@
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
13 changes: 13 additions & 0 deletions src/app/store.ts
@@ -0,0 +1,13 @@
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
reducer: {
counter: counterReducer,
},
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
33 changes: 33 additions & 0 deletions src/features/counter/Counter.tsx
@@ -0,0 +1,33 @@
import { useAppDispatch, useAppSelector } from '../../app/hooks';
import { decrement, increment, incrementAsync } from './counterSlice';

export function Counter() {
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();

return (
<div>
<div>{count}</div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
<button
aria-label="Increment async value"
onClick={() => dispatch(incrementAsync())}
>
Increment async
</button>
</div>
</div>
);
}
63 changes: 63 additions & 0 deletions src/features/counter/counterSlice.ts
@@ -0,0 +1,63 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';

export interface CounterState {
value: number;
}

const initialState: CounterState = {
value: 0,
};

export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},

extraReducers(builder) {
builder
.addCase(incrementAsync.pending, (state) => {
// pending時に1回呼ばれる
console.log('Called when incrementAsync.pending');
})
.addCase(incrementAsync.fulfilled, (state, action) => {
// fulfilledに一回呼ばれる
console.log('Called when incrementAsync.fulfilled');
state.value += action.payload;
})
.addCase(incrementAsync.rejected, (state, action) => {
// rejected時に一回呼ばれる
console.log('Called when incrementAsync.rejected');
});
},
});

export const incrementAsync = createAsyncThunk(
`${counterSlice.name}/asyncIncrement`,
async () => {
await wait(1000);
return 1;
}
);

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

const wait = (ms = 0) => {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
14 changes: 7 additions & 7 deletions src/main.tsx
@@ -1,10 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import ReactDOM from 'react-dom/client';
import App from './App';
import { store } from './app/store';
import { Provider } from 'react-redux';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Provider store={store}>
<App />
</React.StrictMode>
)
</Provider>
);