# React 核心语法学习 - 用于快速理解上手
所有的一切都基于您已经非常熟悉 vue 或者其他前端框架,有不错的前端基础
# 一、创建项目
| npx create-react-app helloworld |
| cd helloworld |
| npm start |
vite 创建
本文路由开始的内容是 vite 创建的项目
| pnpm create vite helloworld --template react |
# 二、基本语法
# 1、插值
| import "./App.css"; |
| |
| const divTitle = '标题' |
| const divContent = '内容' |
| |
| function App() { |
| return ( |
| <div title={divTitle}> |
| {divContent} |
| </div> |
| ); |
| } |
| |
| export default App; |
# 2、条件渲染
| import "./App.css"; |
| |
| const divTitle = "标题"; |
| const divContent = "内容"; |
| |
| let flag = true; |
| |
| const handleFlag = () => { |
| return flag ? <h1>{ divTitle}</h1> : <h1>{divContent}</h1> |
| } |
| |
| function App() { |
| return ( |
| <> |
| <div title={divTitle}>{handleFlag()}</div> |
| </> |
| ); |
| } |
| |
| export default App; |
| import "./App.css"; |
| import { Fragment } from "react"; |
| |
| const list = [ |
| { |
| id: 1, |
| name: "React", |
| }, |
| { |
| id: 2, |
| name: "Angular", |
| }, |
| { |
| id: 3, |
| name: "Vue", |
| }, |
| ]; |
| |
| const mapList = () => { |
| return list.map((item) => (<Fragment key={item.id}><li >{item.name}</li><li>--------</li></Fragment>)); |
| }; |
| |
| function App() { |
| return ( |
| <> |
| <ul>{mapList()}</ul> |
| </> |
| ); |
| } |
| |
| export default App; |
fragment 我觉得可以当作 vue 的 template 去理解
# 3、事件处理
| import "./App.css"; |
| |
| const handleClick = (e) => { |
| console.log('我点击了按钮',e); |
| } |
| |
| |
| function App() { |
| return ( |
| <> |
| <button onClick={handleClick}>按钮</button> |
| </> |
| ); |
| } |
| |
| export default App; |
# 4、useState 状态处理
| import "./App.css"; |
| import { useState } from "react"; |
| |
| function App() { |
| const [data, setData] = useState({ |
| flag: true, |
| }); |
| const handleClick = (e) => { |
| setData({ |
| ...data, |
| flag: !data.flag, |
| }); |
| }; |
| |
| const showContent = () => { |
| return data.flag ? "我是内容" : "我是隐藏的内容"; |
| }; |
| |
| return ( |
| <> |
| <button onClick={handleClick}>按钮</button> |
| <span>{showContent()}</span> |
| </> |
| ); |
| } |
| |
| export default App; |
# 三、组件传值
# 1、dom 传值
| import image from './logo.svg'; |
| |
| function App() { |
| |
| const imageData = { |
| className: 'small', |
| style: { |
| width: 200, |
| height: '100px', |
| backgroundColor: 'gray' |
| }, |
| src: image, |
| } |
| |
| return ( |
| <div> |
| <img alt="" {...imageData}></img> |
| </div> |
| ); |
| } |
| |
| export default App; |
# 2、自定义组件传值
| import image from "./logo.svg"; |
| import { useState } from "react"; |
| |
| |
| function Article({ title, content,children }) { |
| return ( |
| <div> |
| <h1>{title}</h1> |
| {children} |
| <p>{content}</p> |
| </div> |
| ); |
| } |
| |
| function App() { |
| const [articleData, setArticleData] = useState({ |
| title: "React", |
| content: "用于构建用户界面的 JavaScript 库", |
| }); |
| |
| const changeArticleData = () => { |
| setArticleData({ |
| ...articleData, |
| title: "vue", |
| content: "渐进式JavaScript框架", |
| }); |
| }; |
| |
| const imageData = { |
| className: "small", |
| style: { |
| width: 200, |
| height: "100px", |
| backgroundColor: "gray", |
| }, |
| src: image, |
| }; |
| |
| return ( |
| <div> |
| <button onClick={changeArticleData}>按钮</button> |
| <img alt="" {...imageData}></img> |
| <Article {...articleData} > |
| <p>这是一个子组件</p> |
| </Article> |
| </div> |
| ); |
| } |
| |
| export default App; |
![image-20240303173417464]()
# 四、hooks
# 1、useReducer
| import { useReducer } from "react"; |
| |
| |
| * @description: |
| * @param {*} state 当前 reducer 管理的状态 |
| * @param {*} action 对状态的操作 |
| * @return {*} |
| */ |
| function countReducer(state, action) { |
| switch (action.type) { |
| case "increment": |
| return state + 1; |
| case "decrement": |
| return state - 1; |
| default: |
| throw new Error("未知的操作类型"); |
| } |
| } |
| |
| function App() { |
| |
| const [state, dispatch] = useReducer(countReducer, 0) |
| |
| |
| const handleIncrement = () => { |
| dispatch({ type: "increment" }); |
| }; |
| const handleDecrement = () => { |
| dispatch({ type: "decrement" }); |
| }; |
| return ( |
| <div> |
| <h1>计数器</h1> |
| <p>{state}</p> |
| <button onClick={handleIncrement}>增加</button> |
| <button onClick={handleDecrement}>减少</button> |
| </div> |
| ); |
| } |
| |
| export default App; |
| const initialState = {count: 0}; |
| |
| function reducer(state, action) { |
| switch (action.type) { |
| case 'increment': |
| return {count: state.count + 1}; |
| case 'decrement': |
| return {count: state.count - 1}; |
| default: |
| throw new Error(); |
| } |
| } |
| |
| function Counter() { |
| const [state, dispatch] = useReducer(reducer, initialState); |
| return ( |
| <> |
| Count: {state.count} |
| <button onClick={() => dispatch({type: 'decrement'})}>-</button> |
| <button onClick={() => dispatch({type: 'increment'})}>+</button> |
| </> |
| ); |
| } |
肯定会有友友问 useReducer 是什么,我找了一篇文章
一文搞懂 useReducer - 掘金 (juejin.cn)
# 2、useRef
| import { useReducer, useRef, useState } from "react"; |
| |
| |
| * @description: |
| * @param {*} state 当前 reducer 管理的状态 |
| * @param {*} action 对状态的操作 |
| * @return {*} |
| */ |
| function countReducer(state, action) { |
| switch (action.type) { |
| case "increment": |
| return state + 1; |
| case "decrement": |
| return state - 1; |
| default: |
| throw new Error("未知的操作类型"); |
| } |
| } |
| |
| function App() { |
| |
| |
| const [count, setCount] = useState(0) |
| const preCount = useRef(count) |
| |
| const handleIncrement = () => { |
| |
| setCount(count + 1) |
| preCount.current = count |
| }; |
| const handleDecrement = () => { |
| |
| setCount(count - 1) |
| preCount.current = count; |
| |
| }; |
| return ( |
| <div> |
| <h1>计数器</h1> |
| <p>当前:{count}</p> |
| <p>上一次:{preCount.current}</p> |
| <button onClick={handleIncrement}>增加</button> |
| <button onClick={handleDecrement}>减少</button> |
| </div> |
| ); |
| } |
| |
| export default App; |
React useRef 指南 - 掘金 (juejin.cn)
# 五、React Router
在开始之前 | React Router6 中文文档 (baimingxuan.github.io)
# 1、简单使用
| |
| |
| |
| |
| |
| |
| import { createBrowserRouter, RouterProvider } from 'react-router-dom' |
| |
| const router = createBrowserRouter([ |
| { |
| path: '/', |
| element: <div>hello world</div>, |
| }, |
| ]) |
| |
| ReactDOM.createRoot(document.getElementById('root')).render( |
| <React.StrictMode> |
| <RouterProvider router={router} /> |
| </React.StrictMode> |
| ) |
# 2、抽离组件
| import React from 'react' |
| import ReactDOM from 'react-dom/client' |
| import { |
| createBrowserRouter, |
| RouterProvider |
| } from 'react-router-dom' |
| import './index.css' |
| |
| import Root from '@/router/root' |
| import ErrorPage from '@/error-page' |
| |
| const router = createBrowserRouter([ |
| { |
| path: '/', |
| element: <Root />, |
| errorElement: <ErrorPage /> |
| }, |
| ]) |
| |
| ReactDOM.createRoot(document.getElementById('root')).render( |
| <React.StrictMode> |
| <RouterProvider router={router} /> |
| </React.StrictMode> |
| ) |
| |
| |
| import { useRouteError } from "react-router-dom"; |
| |
| export default function ErrorPage() { |
| const error = useRouteError(); |
| console.error(error); |
| |
| return ( |
| <div id="error-page"> |
| <h1>Oops!</h1> |
| <p>Sorry, an unexpected error has occurred.</p> |
| <p> |
| <i>{error.statusText || error.message}</i> |
| </p> |
| </div> |
| ); |
| } |
路由的编码可以从 main.jsx 中抽离出来
router/index.jsx
| import { createBrowserRouter } from "react-router-dom"; |
| import Root, { |
| loader as rootLoader, |
| action as rootAction, |
| } from "@/router/root"; |
| import ErrorPage from "@/error-page"; |
| import Contact from "@/router/contact"; |
| |
| |
| const router = createBrowserRouter([ |
| { |
| path: "/", |
| element: <Root />, |
| errorElement: <ErrorPage />, |
| loader: rootLoader, |
| action: rootAction, |
| children: [ |
| { |
| path: "/contacts/:contactId", |
| element: <Contact />, |
| }, |
| ], |
| }, |
| ]); |
| |
| export default router |
main.jsx
| import React from 'react' |
| import ReactDOM from 'react-dom/client' |
| import './index.css' |
| import { RouterProvider } from 'react-router-dom' |
| import router from '@/router' |
| |
| ReactDOM.createRoot(document.getElementById('root')).render( |
| <React.StrictMode> |
| <RouterProvider router={router} /> |
| </React.StrictMode> |
| ) |
# 3、路由模式
和 vue 一样,也是 hash 和 history 两种路由模式。
createBrowserRouter
:history 需要后端配合
createHashRouter
: hash 不需要后端支持,兼容性更好
# 4、跳转
index.jsx
| import { Button } from "antd"; |
| import useRouterHooks from "@/hooks/routerHooks"; |
| |
| export default function IndexAndHome() { |
| const { goto } = useRouterHooks(); |
| |
| return ( |
| <div> |
| <h1>首页</h1> |
| <p>我是首页</p> |
| <Button type="primary" onClick={() => goto("/login")}> |
| login |
| </Button> |
| </div> |
| ); |
| } |
login.jsx
| import { Button } from 'antd'; |
| import useRouterHooks from '@/hooks/routerHooks'; |
| |
| export default function Login() { |
| const { goto } = useRouterHooks(); |
| return ( |
| <div> |
| <h1>登录</h1> |
| <p>我是登录</p> |
| <Button onClick={() => goto('/')}>登录</Button> |
| </div> |
| ); |
| } |
router/index.jsx
| import { createBrowserRouter } from "react-router-dom"; |
| import ErrorPage from "@/error-page"; |
| import IndexAndHome from "@/views"; |
| import Login from "@/views/login"; |
| |
| const router = createBrowserRouter([ |
| { |
| path: "/", |
| element: <IndexAndHome />, |
| errorElement: <ErrorPage />, |
| }, |
| { |
| path: "/login", |
| element: <Login />, |
| errorElement: <ErrorPage />, |
| }, |
| ]); |
| |
| export default router; |
hooks/routerHooks.jsx
| import { useNavigate } from "react-router-dom"; |
| |
| export default function useRouterHooks() { |
| const navigate = useNavigate(); |
| |
| |
| * @description: jump to the specified page |
| * @param {*} path |
| * @return {*} |
| */ |
| function goto(path) { |
| navigate(path); |
| } |
| return { |
| goto, |
| }; |
| } |
navigate 函数第二个参数是一个对象
其中有一个属性 replace
,为 true 时会导致导航替换历史堆栈中的当前条目,我们升级 routerHooks
| import { useNavigate } from "react-router-dom"; |
| |
| export default function useRouterHooks() { |
| const navigate = useNavigate(); |
| |
| |
| * @description: jump to the specified page |
| * @param {*} path |
| * @param {*} replace default false | whether to replace the current page |
| * @return {*} |
| */ |
| function goto(path, replace = false) { |
| navigate(path, { replace }); |
| } |
| return { |
| goto, |
| }; |
| } |
在一些场景中,比如登录之后从历史记录中干掉登录页面或者进登录干掉之前页面的记录也是有需求的。
# 5、路由传参
# 5.1 searchParams
| navigate('/about?id=1001') |
拿取参数
| import { useSearchParams } from 'react-router-dom' |
| |
| export default function About(){ |
| const [params] = useSearchParams() |
| console.log(params) |
| const id = params.get('id') |
| } |
# 5.2 params
router 配置文件里也要配置
| const router = createBrowserRouter([ |
| { |
| path: '/about/:id', |
| element: <About /> |
| } |
| ]) |
拿取参数
| import {useParams} from 'react-router-dom' |
| |
| export default function About(){ |
| const params = useParams() |
| console.log(params.id) |
| } |
现在我们再次升级我们的 routerHooks,用于支持更加简便的获取参数
hooks/routerHooks
| import { useNavigate, useSearchParams, useParams } from "react-router-dom"; |
| |
| export default function useRouterHooks() { |
| const navigate = useNavigate(); |
| const [searchParams, setSearchParams] = useSearchParams(); |
| const params = useParams(); |
| |
| |
| * @description: jump to the specified page |
| * @param {*} path |
| * @param {*} replace default false | whether to replace the current page |
| * @return {*} |
| */ |
| function goto(path, replace = false) { |
| navigate(path, { replace }); |
| } |
| |
| function getSearchParams() { |
| return { searchParams, setSearchParams }; |
| } |
| |
| function getParams() { |
| return params; |
| } |
| return { |
| goto, |
| getSearchParams, |
| getParams, |
| }; |
| } |
views/index.jsx
| import { Button } from "antd"; |
| import useRouterHooks from "@/hooks/routerHooks"; |
| |
| export default function IndexAndHome() { |
| const { goto } = useRouterHooks(); |
| |
| return ( |
| <div> |
| <h1>首页</h1> |
| <p>我是首页</p> |
| <Button type="primary" onClick={() => goto("/login?name=anixuil")}> |
| login |
| </Button> |
| </div> |
| ); |
| } |
views/login.jsx
| import { Button } from "antd"; |
| import useRouterHooks from "@/hooks/routerHooks"; |
| |
| export default function Login() { |
| const { goto, getSearchParams } = useRouterHooks(); |
| |
| const { searchParams } = getSearchParams(); |
| |
| return ( |
| <div> |
| <h1>登录</h1> |
| <p>我是登录</p> |
| <p>name: {searchParams.get("name")}</p> |
| <Button onClick={() => goto("/person/1140040227", true)}>登录</Button> |
| </div> |
| ); |
| } |
views/person.jsx
| import useRouterHooks from "@/hooks/routerHooks"; |
| import { Button } from "antd"; |
| |
| export default function Person() { |
| |
| const { goto, getParams } = useRouterHooks(); |
| const { id } = getParams(); |
| |
| return ( |
| <div> |
| <h1>个人中心</h1> |
| <p>我是个人中心</p> |
| <p>id: {id}</p> |
| <Button type="primary" onClick={() => goto('/')}>go Index</Button> |
| </div> |
| ); |
| } |
router/index.jsx
| import { createBrowserRouter } from "react-router-dom"; |
| import ErrorPage from "@/error-page"; |
| import IndexAndHome from "@/views"; |
| import Login from "@/views/login"; |
| import Person from "@/views/person"; |
| |
| const router = createBrowserRouter([ |
| { |
| path: "/", |
| element: <IndexAndHome />, |
| errorElement: <ErrorPage />, |
| }, |
| { |
| path: "/login", |
| element: <Login />, |
| errorElement: <ErrorPage />, |
| }, |
| { |
| path: "/person/:id", |
| element: <Person />, |
| errorElement: <ErrorPage />, |
| }, |
| ]); |
| |
| export default router; |
以上便实现了两种方式的路由传参并获取到传参的值展现到页面上
# 6、嵌套路由
- 准备二级路由,在路由配置文件中使用
children
对二级路由进行配置
- 通过内置组件
Outlet
渲染耳机路由组件
- 使用内置组件
Link
进行声明式导航配置
router/index.jsx
| import { createBrowserRouter } from "react-router-dom"; |
| import ErrorPage from "@/error-page"; |
| import IndexAndHome from "@/views"; |
| import Login from "@/views/login"; |
| import Person from "@/views/person"; |
| import Layout from "@/views/layout"; |
| |
| const router = createBrowserRouter([ |
| { |
| path: "/", |
| element: <IndexAndHome />, |
| errorElement: <ErrorPage />, |
| }, |
| { |
| path: "/login", |
| element: <Login />, |
| errorElement: <ErrorPage />, |
| }, |
| { |
| path: "/person/:id", |
| element: <Person />, |
| errorElement: <ErrorPage />, |
| }, |
| { |
| path: '/layout', |
| element: <Layout />, |
| errorElement: <ErrorPage />, |
| children: [ |
| { |
| path: 'message', |
| element: <div>message</div>, |
| }, |
| { |
| path: 'contact', |
| element: <div>contact</div>, |
| } |
| ] |
| } |
| ]); |
| |
| export default router; |
views/layout.jsx
| import { Outlet, Link } from "react-router-dom"; |
| |
| export default function Layout() { |
| return ( |
| <div> |
| <h1>layout</h1> |
| <p>我是layout</p> |
| <Link to="/layout/message">消息</Link> |
| <Link to="/layout/contact">联系人</Link> |
| <Outlet /> |
| </div> |
| ); |
| } |
# 7、默认 404
| import { useRouteError } from "react-router-dom"; |
| |
| export default function ErrorPage() { |
| const error = useRouteError(); |
| console.error(error); |
| |
| return ( |
| <div id="error-page"> |
| <h1>Oops!</h1> |
| <p>Sorry, an unexpected error has occurred.</p> |
| <p> |
| <i>{error.statusText || error.message}</i> |
| </p> |
| </div> |
| ); |
| } |
路由配置里的
| errorElement: <ErrorPage /> |
# 六、Antd
Ant Design - 一套企业级 UI 设计语言和 React 组件库 (antgroup.com)
# 1、安装引入
# 2、使用
| import { Button } from "antd"; |
| |
| ... |
| <Button type="primary">按钮</Button> |
| ... |
# 3、自定义主题
main.jsx
| import { ConfigProvider } from "antd"; |
| |
| ReactDOM.createRoot(document.getElementById("root")).render( |
| <React.StrictMode> |
| <ConfigProvider |
| theme=<!--swig0--> |
| > |
| <RouterProvider router={router} /> |
| </ConfigProvider> |
| </React.StrictMode> |
| ); |
# 1、安装
| pnpm i @reduxjs/toolkit react-redux |
# 2、引入使用
store/index.jsx
| import { configureStore } from "@reduxjs/toolkit"; |
| import counter from "@/store/modules/counter"; |
| |
| export default configureStore({ |
| reducer: { |
| counter, |
| }, |
| }); |
store/modules/counter.jsx
| import { createSlice } from '@reduxjs/toolkit'; |
| |
| const counter = createSlice({ |
| name: 'counter', |
| initialState: { |
| count: 0 |
| }, |
| |
| |
| reducers: { |
| increment: (state,action) => { |
| state.count += action.payload |
| }, |
| decrement: (state,action) => { |
| state.count -= action.payload |
| } |
| } |
| }) |
| |
| |
| export const { increment, decrement } = counter.actions; |
| export default counter.reducer; |
main.jsx
| import React from "react"; |
| import ReactDOM from "react-dom/client"; |
| import "./index.css"; |
| import { RouterProvider } from "react-router-dom"; |
| import router from "@/router"; |
| import { ConfigProvider } from "antd"; |
| import { Provider } from "react-redux"; |
| import store from "@/store"; |
| |
| ReactDOM.createRoot(document.getElementById("root")).render( |
| <React.StrictMode> |
| <ConfigProvider |
| theme=<!--swig1--> |
| > |
| <Provider store={store}> |
| <RouterProvider router={router} /> |
| </Provider> |
| </ConfigProvider> |
| </React.StrictMode> |
| ); |
views/index.jsx
| import { Button } from "antd"; |
| import useRouterHooks from "@/hooks/routerHooks"; |
| import { useDispatch, useSelector } from "react-redux"; |
| |
| export default function IndexAndHome() { |
| const { goto } = useRouterHooks(); |
| |
| const dispatch = useDispatch(); |
| const { count } = useSelector((state) => state.counter); |
| return ( |
| <div> |
| <h1>首页</h1> |
| <p>我是首页</p> |
| <Button type="primary" onClick={() => goto("/login?name=anixuil")}> |
| login |
| </Button> |
| <p>counter: {count}</p> |
| <Button |
| onClick={() => dispatch(increment(1))} |
| > |
| + |
| </Button> |
| <Button |
| onClick={() => dispatch(decrement(1))} |
| > |
| - |
| </Button> |
| </div> |
| ); |
| } |
![image-20240309201406178]()
# 3、异步
store/modules/counter.jsx
| import { createSlice } from "@reduxjs/toolkit"; |
| |
| const counter = createSlice({ |
| name: "counter", |
| initialState: { |
| |
| count: 0, |
| }, |
| |
| |
| reducers: { |
| |
| increment: (state, action) => { |
| state.count += action.payload; |
| }, |
| decrement: (state, action) => { |
| state.count -= action.payload; |
| }, |
| }, |
| }); |
| |
| |
| export const { increment, decrement } = counter.actions; |
| |
| |
| export const incrementAsync = (amount) => (dispatch) => { |
| setTimeout(() => { |
| dispatch(increment(amount)); |
| }, 1000); |
| }; |
| export const decrementAsync = (amount) => (dispatch) => { |
| setTimeout(() => { |
| dispatch(decrement(amount)); |
| }, 1000); |
| }; |
| |
| export default counter.reducer; |
views/index.jsx
| import { Button } from "antd"; |
| import useRouterHooks from "@/hooks/routerHooks"; |
| import { useDispatch, useSelector } from "react-redux"; |
| import { increment, decrement, incrementAsync, decrementAsync } from "../store/modules/counter"; |
| |
| export default function IndexAndHome() { |
| const { goto } = useRouterHooks(); |
| |
| const dispatch = useDispatch(); |
| const { count } = useSelector((state) => state.counter); |
| return ( |
| <div> |
| <h1>首页</h1> |
| <p>我是首页</p> |
| <Button type="primary" onClick={() => goto("/login?name=anixuil")}> |
| login |
| </Button> |
| <p>counter: {count}</p> |
| <Button |
| onClick={() => dispatch(increment(1))} |
| > |
| + |
| </Button> |
| <Button |
| onClick={() => dispatch(decrement(1))} |
| > |
| - |
| </Button> |
| <Button |
| onClick={() => dispatch(incrementAsync(1))} |
| > |
| async + |
| </Button> |
| <Button |
| onClick={() => dispatch(decrementAsync(1))} |
| > |
| async - |
| </Button> |
| </div> |
| ); |
| } |
# 4、复杂类型实现
数组元素的添加删除
store/modules/counter.jsx
| import { createSlice } from "@reduxjs/toolkit"; |
| |
| const counter = createSlice({ |
| name: "counter", |
| initialState: { |
| |
| count: 0, |
| list: [], |
| }, |
| |
| |
| reducers: { |
| |
| increment: (state, action) => { |
| state.count += action.payload; |
| }, |
| decrement: (state, action) => { |
| state.count -= action.payload; |
| }, |
| push: (state, action) => { |
| state.list.push(action.payload); |
| }, |
| del: (state, action) => { |
| state.list.splice(action.payload, 1); |
| }, |
| }, |
| }); |
| |
| |
| export const { increment, decrement, push, del } = counter.actions; |
| |
| |
| export const incrementAsync = (amount) => (dispatch) => { |
| setTimeout(() => { |
| dispatch(increment(amount)); |
| }, 1000); |
| }; |
| export const decrementAsync = (amount) => (dispatch) => { |
| setTimeout(() => { |
| dispatch(decrement(amount)); |
| }, 1000); |
| }; |
| |
| export default counter.reducer; |
views/index.jsx
| import { Button } from "antd"; |
| import useRouterHooks from "@/hooks/routerHooks"; |
| import { useDispatch, useSelector } from "react-redux"; |
| import { increment, decrement, incrementAsync, decrementAsync, push, del } from "../store/modules/counter"; |
| |
| export default function IndexAndHome() { |
| const { goto } = useRouterHooks(); |
| |
| const dispatch = useDispatch(); |
| const { count, list } = useSelector((state) => state.counter); |
| return ( |
| <div> |
| <h1>首页</h1> |
| <p>我是首页</p> |
| <Button type="primary" onClick={() => goto("/login?name=anixuil")}> |
| login |
| </Button> |
| <p>counter: {count}</p> |
| <p>list: {list.join(",")}</p> |
| <Button onClick={() => dispatch(increment(1))}>+</Button> |
| <Button onClick={() => dispatch(decrement(1))}>-</Button> |
| <Button onClick={() => dispatch(incrementAsync(1))}>async +</Button> |
| <Button onClick={() => dispatch(decrementAsync(1))}>async -</Button> |
| <Button onClick={() => dispatch(push(parseInt(Math.random() * 100)))}> |
| push |
| </Button> |
| <Button onClick={() => dispatch(del(list.length - 1))}>del</Button> |
| </div> |
| ); |
| } |