react 总结 三

组件通信

父子组件之间通讯

  1. 父传子

 父组件的代码

import React,{ Component } from "react";
import Sub from "./SubComponent.js";
import "./App.css";

export default class App extends Component{

    render(){
        return(
            <div>
                <Sub title = "今年过节不收礼" />
            </div>
        )
    }
}

子组件

import React from "react";

const Sub = (props) => {
    return(
        <h1>
            { props.title }
        </h1>
    )
}

export default Sub;

子组件向父组件通信

利用回调函数,可以实现子组件向父组件通信:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。

SubComponent.js:

import React from "react";

const Sub = (props) => {
    const cb = (msg) => {
        return () => {
            props.callback(msg)
        }
    }
    return(
        <div>
            <button onClick = { cb("我们通信把") }>点击我</button>
        </div>
    )
}

export default Sub;

APP.js:

import React,{ Component } from "react";
import Sub from "./SubComponent.js";
import "./App.css";

export default class App extends Component{
    callback(msg){
        console.log(msg);
    }
    render(){
        return(
            <div>
                <Sub callback = { this.callback.bind(this) } />
            </div>
        )
    }
}

跨级组件通信 祖向后代传

采用下面两种方式:

  • 中间组件层层传递 props
  • 使用 context 对象

对于第一种方式,如果父组件结构较深,那么中间的每一层组件都要去传递 props,增加了复杂度,并且这些 props 并不是这些中间组件自己所需要的。不过这种方式也是可行的,当组件层次在三层以内可以采用这种方式,当组件嵌套过深时,采用这种方式就需要斟酌了。
使用 context 是另一种可行的方式,context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。
使用 context 也很简单,需要满足两个条件:

  • 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
  • 子组件要声明自己需要使用 context

1)创建context容器对象:

const xxxContext = React.createcontext()
 const {Provider}= xxxContext

2)渲染子组时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据:

<Provider value={数据}> 
子组件
 </Provider>

3)后代组件读取数据:

//第一种方式:仅适用于类组件

static contextType = xxxContext //声明接收contextthis.context
 //读取context中的value数据

//第二种方式:函崴数组件与类组件都可以

<XxxContext.Consumer> { value => (
 // value就是context中的value数据要显示的内容 )
 } </xxxContext.Consumer>

非嵌套组件间通信

兄弟节点的共同⽗节点,由⽗节点转发信息,实现兄弟间通信。

消息订阅与发布 pubsub

可以借助 Redux 或 Mobx 等全局状态管理⼯具进⾏通信,它们会维护⼀个全局状态中⼼(Store),并可以根据不同的事件产⽣新的状态。

router

路由的基本使用

路由分为两种 BrowserRouter 与 HashRouter

1.明确好界面中的导航区、展示区

2.导航区的a标签改为Link标签

<Link to=”/xxxxx" >Demo</Link>

3.展示区写Route标签进行路径的匹配

<Route path= ' /xxxx' component={Demo}/>

4.的最外侧包裹了一个<BrowserRouter>或<HashRouter>

案例 首先创建两个组件,然后在App.js文件中应用并使用, 接着在到index.js文件中注册App组件,最后用index.html文件渲染到页面上

App,js文件

import React, { Component } from 'react';
import { Link,Route } from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap'
export default class App extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    <div className="col-xs-offset-2 col-xs-8">
                        <div className="page-header"><h2>React Router Demo</h2></div>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-2 col-xs-offset-2">
                        <div className="list-group">
                            {/* 原生html 要靠<a>跳转不同的页面 */}
                            {/* <a className="list-group-item" href="./about.html">About</a>
                            <a className="list-group-item active" href="./home.html">Home</a> */}

                            {/* 在React中靠路由链接实现切换组件 */}
                            <Link className="list-group-item" to="/home">Home</Link>
                            <Link className="list-group-item" to="/about">About</Link>
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                            <div className="panel-body">
                                {/* 注册路由 */}
                                <Route path="/home" component={Home}/>
                                <Route path="/about" component={About}/>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
组件一:Home
import React, { Component } from 'react'

export default class Home extends Component {
    render() {
        return (
            <h3>我是Home的内容</h3>
        )
    }
}
组件一:About
import React, { Component } from 'react'

export default class About extends Component {
    render() {
        return (
            <h3>我是About的内容</h3>
        )
    }
}
index.js文件

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import {BrowserRouter} from 'react-router-dom'

ReactDOM.render(
    <BrowserRouter> // <App>的最外侧包襄了一个<BrowserRouter>或<HashRouter>
        <App/>
    </BrowserRouter>,
    document.getElementById('root')
)

路由的扩展使用

NavLink与封装NavLink

1.NavLink 可以实现路由链接的高亮,通过activeclassName指定样式名

2.标签体内容是一个特殊的标签属性

3.通过this.props.children可以获取标签体内容

App.js文件修改的代码
{/* 在React中靠路由链接实现切换组件 */}
<NavLink activeClassName="add" className="list-group-item" to="/home">Home</NavLink>
<NavLink activeClassName="add" className="list-group-item" to="/about">About</NavLink>
这里用 activeClassName="add" 来控制按钮高亮的颜色显示


渲染文件 index.html 文件修改后的代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./css/bootstrap.css">
    <style>
        .add{
            background-color: rgb(209,137,4) !important; // 因为 bootstrap 的权重比较高,所以要用!important
            color:white !important;
        }
    </style>
</head>
<body>
    <div id="root"></div>
</body>
</html>

NavLink 比 Link 好用

Switch的使用

1.通常情况下,path和lcomponent是一一对应的关系。

2.Switch可以提高路由匹配效率(单一匹配)。

import React, { Component } from 'react'
import {Route,Switch} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'
import Test from './pages/Test1'
 
export default class App extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    <div className="col-xs-offset-2 col-xs-8">
                        <Header/>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-2 col-xs-offset-2">
                        <div className="list-group">
 
                            {/* 原生html中,靠<a>跳转不同的页面 */}
                            {/* <a className="list-group-item" href="./about.html">About</a>
                            <a className="list-group-item active" href="./home.html">Home</a> */}
 
                            {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
                            {/* <NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
                            <NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink> */}
                            <MyNavLink to="/about">About</MyNavLink>
                            <MyNavLink to="/Home">Home</MyNavLink>
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                            <div className="panel-body">
                                {/* 注册路由 */}
                                <Switch>
                                    <Route path="/about" component={About}/>
                                    <Route path="/home" component={Home}/>
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="abc" component={ABC} />
                                    <Route path="/Home" component={Test}/>
                                </Switch>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}

上述代码使用封装好的MyNavLink分别指向了

/about

/home

然后使用Route来设置对应的地址展示的路由组件

Switch作用体现:

由于路由的默认是多次匹配(既:通过for循环遍历一遍每个路由组件,找到所有对应该路径的路由组件来展,如果路由组件很多的话造成了效率过低)

这里用Switch包裹Route来实现单一匹配,既一个路由对应第一个该路由的路由组件。原理为遍历到该路由组件后,停止遍历。

解决多级路径刷新页面样式丢失的问题

1.public/index.htm1 中引入样式时不写﹒/ 写/(常用)

2.public/index.htm1 中引入样式时不写﹒/写%PUBLIC_URL%(常用)

3.使用HashRouter 要少用

路由的严格匹配与模糊匹配

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

2.开启严格匹配:

3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

注册路由 默认模糊匹配 添加exact为精确匹配看需要使用

Redirect的使用

1.一股写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路山

2.具体编码:

<Switch>
<Route path=" / about" component={About}/>
<Route path=" / home" component={Home}/><Redirect to=" / about" />
</ Switch>
嵌套路由

1.注册子路由时要写上父路由的path值

2.路由的匹配是按照注册路由的顺序进行的

向路由组件传递参数
1.params参数
    路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
    注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
    接收参数:this.props.match.params
2.search参数
    路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
    注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数:this.props.location.search
    备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数
    路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
    注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数:this.props.location.state
    备注:刷新也可以保留住参数
编程式路由跳转

push与repalce

push跳转会留下痕迹可以后退 但repalce不会

借助this.prosp.history对象上的API对操作路由跳转、前进、后退
	this.prosp.history.push()
	this.prosp.history.replace()
	this.prosp.history.goBack()
	this.prosp.history.goForward()
	this.prosp.history.go()
 withRouter 的使用

withRouter 使 非路由组件拥有路由组件所特有的特性

withRouter可以加工一般组件,让一般组件具备路由组件所特有的API

withRouter的返回值是一个新组件

Header 组件代码

import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'

 class Header extends Component {
    // 回退
    back = () => {
        this.props.history.goBack()
    }

    // 前进
    forward = () => {
        this.props.history.goForward()
    }

    /// go
    go = () => {
        this.props.history.go(2)
    }

    render() {
        // console.log('一般组件',this.props)
        return (
            <div className="page-header">
                <h2>React Router Demo</h2>
                <button onClick={this.back}>回退</button>
                <button onClick={this.forward}>前进</button>
                <button onClick={this.go}>go</button>
            </div>
        )
    }
}

export default withRouter(Header)
 BrowserRouter与HashRouter的区别
1.底层原理不一样:
    BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
    HashRouter使用的是URL的哈希值。
2.path表现形式不一样
    BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
    HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
    1).BrowserRouter没有任何影响,因为state保存在history对象中。
    2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。

redux 

Redux设计和使用的三项原则:

store是唯一的、

只有store能够改变自己的内容、

Reducer必须是纯函数。

纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何副作用。

redux流程原理

在 React 中,组件连接到 redux ,如果要访问 redux,需要派出一个包含 id 和负载 (payload) 的 action。action 中的 payload 是可选的,action 将其转发给 Reducer。

当 reducer 收到 action 时,通过 switch...case 语法比较 action 中 type。 匹配时,更新对应的内容返回新的 state。

当 Redux 状态更改时,连接到 Redux 的组件将接收新的状态作为 props。当组件接收到这些 props 时,它将进入更新阶段并重新渲染 UI。

reducer可以接受state,但是绝不能修改state。

配置与使用Redux

配置Redux开发环境的最快方法是使用create-react-app工具。在开始之前,确保已经安装并更新了nodejs,npm。

npm install redux

创建store目录,在store目录下新建index.js文件,键入以下内容:

/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
//引入为Count组件服务的reducer
import reducer from './reducer';//暴露store
export default createStore(reducer)

在store目录下创建reducer.js文件,键入以下内容:

const defaultState = 0//reducer可以接收state,但是不能修改state
export default (state = defaultState,action) => {
const { type, data } = action;
switch (type) {case "increment":
return data + state;
default:
return state;}}

在组件中就可以使用store的数据

import React, { Component } from "react";
import store from "../store/index";
import "./index.css";
export default class index extends Component {
getIncrement = () => {
let val = this.getInpVal.valuestore.dispatch(
{type: "increment",
data: val*1,
});
};
getDecrement = () => {
let val = this.getInpVal.value
store.dispatch({
type: "decrement",
data: val*1,
});
};
getIncrementOdd = () => {
if (store.getState()%2!=0) {
let val = this.getInpVal.valuestore.dispatch(
{type: "increment",
data: val1,
});
}
};
getIncrementOAsync = () => {
setTimeout(() => {
let val = this.getInpVal.value
store.dispatch({
type: "increment",
data: val*1,
});
}, 1000);
};
render() {
return (
<div>
<h3>累加和为:{store.getState()}</h3>
<select ref={c => this.getInpVal = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.getIncrement}>+</button>
<button onClick={this.getDecrement}>-</button>
<button onClick={this.getIncrementOdd}>奇数加</button>
<button onClick={this.getIncrementOAsync}>异步加</button>
</div>
);
}
}

扩展

setState 扩展

(1). setstate(statechange,[callback]) ------对象式的setstate

1.statechange为状态改变对象(该对象可以体现出状态的更改)

2.callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用,可以拿到最新值

(2). setstate(updater ,[callback])------函数式的setstate

1.updater为返回statechange对象的函|数。

2.updater可以接收到state和props。

4.callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用。

总结:

1.对象式的setstate是函数数式的setstate的简写方式(语法糖)

2.使用原则:

(1).如果新状态不依赖于原状态===>使用对象方式

(2).如果新状态依赖于原状态===>使用函数方式

(3).如果需要在setState(O)执行后获取最新的状态数据,

要在第二个callback函数中读取

懒加载 lzayload

通常,使用静态导入声明组件:

import MarkdownPreview from './MarkdownPreview.js';

若要延迟加载此组件的代码,直到首次呈现,请将此导入替换为:

import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));

现在,组件的代码已按需加载,您还需要指定加载时应显示的内容。您可以通过将惰性组件或其任何父组件包装到边界中来执行此操作:

<Suspense fallback={<Loading />}> //未加载出的时候的显示
 <h2>Preview</h2> 
  <MarkdownPreview />
 </Suspense>

Hooks

useState

useState 用于在函数组件中调用给组件添加一些内部状态 state,正常情况下纯函数不能存在状态副作用,通过调用该 Hook 函数可以给函数组件注入状态 state

useState 唯一的参数就是初始 state,会返回当前状态和一个状态更新函数,并且 useState 返回的状态更新函数不会把新的 state 和旧的 state 进行合并,如需合并可使用 ES6 的对象结构语法进行手动合并

const [state, setState] = useState(initialState);
等价 class 示例

useState 返回的状态类似于 class 组件在构造函数中定义 this.state,返回的状态更新函数类似于 class 组件的 this.setState

import React from 'react';

export default class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <button onClick={() => this.setState({ count: this.state.count - 1 })}>-</button>
        <input type="text" value={this.state.count} onChange={(e) => this.setState({ count: e.target.value })} />
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>+</button>
      </div>
    );
  }
}
函数式更新

如果新的 state 需要通过使用先前的 state 计算得出,可以往 setState 传递函数,该函数将接收先前的 state,并返回一个更新后的值

import React, { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0);

  const lazyAdd = () => {
    setTimeout(() => {
      // 每次执行都会最新的state,而不是使用事件触发时的state
      setCount(count => count + 1);
    }, 3000);
  } 

  return (
    <div>
      <p>the count now is {count}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
      <button onClick={lazyAdd}>lazyAdd</button>
    </div>
  );
}
惰性初始 state

如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只会在初始渲染时被调用

import React, { useState } from 'react'

export default function Counter(props) {
  // 函数只在初始渲染时执行一次,组件重新渲染时该函数不会重新执行
  const initCounter = () => {
    console.log('initCounter');
    return { number: props.number };
  };
  const [counter, setCounter] = useState(initCounter);

  return (
    <div>
      <button onClick={() => setCounter({ number: counter.number - 1 })}>-</button>
      <input type="text" value={counter.number} onChange={(e) => setCounter({ number: e.target.value})} />
      <button onClick={() => setCounter({ number: counter.number + 1 })}>+</button>
    </div>
  );
}
useEffect

在函数组件主体内(React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性

useEffect Hook 的使用则是用于完成此类副作用操作。useEffect 接收一个包含命令式、且可能有副作用代码的函数

useEffect函数会在浏览器完成布局和绘制之后,下一次重新渲染之前执行,保证不会阻塞浏览器对屏幕的更新

import React, { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  // useEffect 内的回调函数会在初次渲染后和更新完成后执行
  // 相当于 componentDidMount 和 componentDidUpdate
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>count now is {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}
等价 class 示例
import React from 'react';

export default class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>count now is {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>+</button>
      </div>
    );
  }
}
清除 effect

通常情况下,组件卸载时需要清除 effect 创建的副作用操作,useEffect Hook 函数可以返回一个清除函数,清除函数会在组件卸载前执行。组件在多次渲染中都会在执行下一个 effect 之前,执行该函数进行清除上一个 effect

清除函数的执行时机类似于 class 组件componentDidUnmount 生命周期,这的话使用 useEffect 函数可以将组件中互相关联的部分拆分成更小的函数,防止遗忘导致不必要的内存泄漏

import React, { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('start an interval timer')
    const timer = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
    // 返回一个清除函数,在组件卸载前和下一个effect执行前执行
    return () => {
      console.log('destroy effect');
      clearInterval(timer);
    };
  }, []);

  return (
    <div>
      <p>count now is {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}
优化 effect 执行

默认情况下,effect 会在每一次组件渲染完成后执行。useEffect 可以接收第二个参数,它是 effect 所依赖的值数组,这样就只有当数组值发生变化才会重新创建订阅。但需要注意的是:

  • 确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量
  • 传递一个空数组作为第二个参数可以使 effect 只会在初始渲染完成后执行一次
    import React, { useState, useEffect } from 'react';
    
    export default function Counter() {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      }, [count]); // 仅在 count 更改时更新
    
      return (
        <div>
          <p>count now is {count}</p>
          <button onClick={() => setCount(count + 1)}>+</button>
        </div>
      );
    }
    useContext

Context 提供了一个无需为每层组件手动添加 props ,就能在组件树间进行数据传递的方法,useContext 用于函数组件中订阅上层 context 的变更,可以获取上层 context 传递的 value prop 值

useContext 接收一个 context 对象(React.createContext的返回值)并返回 context 的当前值,当前的 context 值由上层组件中距离当前组件最近的 的 value prop 决定

import React, { useContext, useState } from 'react';

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

// 为当前 theme 创建一个 context
const ThemeContext = React.createContext();

export default function Toolbar(props) {
  const [theme, setTheme] = useState(themes.dark);

  const toggleTheme = () => {
    setTheme(currentTheme => (
      currentTheme === themes.dark
        ? themes.light
        : themes.dark
    ));
  };

  return (
    // 使用 Provider 将当前 props.value 传递给内部组件
    <ThemeContext.Provider value={{theme, toggleTheme}}>
      <ThemeButton />
    </ThemeContext.Provider>
  );
}

function ThemeButton() {
  // 通过 useContext 获取当前 context 值
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return (
    <button style={{background: theme.background, color: theme.foreground }} onClick={toggleTheme}>
      Change the button's theme
    </button>
  );
}
useReducer

useReducer 作为 useState 的代替方案,在某些场景下使用更加适合,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。

使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为父组件可以向自组件传递 dispatch 而不是回调函数

import React, { useReducer } from 'react'

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();
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值