React_03 学习笔记

6 篇文章 0 订阅

目录

一、状态管理 (redux)

1.1、简介

1.2、安装 Redux

1.3、三大原则

1.4、使用 redux

1.5、代码模块化

二、react-redux

三、redux 中间件

3.1、介绍

3.2、使用 redux-thunk

四、Redux 模块化

五、路由

5.1、介绍

5.2、路由使用

5.2.1、安装路由模块

5.2.2、相关组件

5.3、声明式导航

5.4、编程式导航

5.5、路由参数

5.6、嵌套路由

5.7、三种路由渲染方式

5.7.1、component (组件对象或函数)

5.7.2、render (函数)

5.7.3、children (函数或组件) 

5.8、withRouter 高阶组件

5.9、自定义导航组件

六、过渡动画组件

6.1、给组件使用过渡动画

6.2、列表过渡

6.3、利用高阶组件给组件添加动画

6.4、集合 animate.css

七、immutable.js

7.1、概述

7.2、使用 immutable 优缺点

7.3、安装

7.4、常用 Api

7.5、redux 中集成 immutable

图例 :


跳转链接 =>  React_01 学习笔记

跳转链接 =>  React_02 学习笔记

跳转链接 =>  React_04 菜谱项目

跳转链接 =>  React_05 Hooks


  • 能够使用 redux 进行 数据状态管理
  • 能够使用 redux-thunk 完成 异步请求
  • 能够 redux 模块化 拆分
  • 完成 编程式导航 跳转
  • 掌握 3 种 路由渲染 方式
  • 定义好 路由参数数据 和 获取
  • 能够 自定义导航组件
  • 能够把 iummutable 集成到 redux 中

一、状态管理 (redux)

1.1、简介

自述 · Redux

2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与 函数式编程 结合一起,很短时间内就成为了最热门的 前端架构。

简单说,如果你的 UI 层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。如果你的项目组件的数量和层级也变得越来越多,越来越深,此时组件间的 数据通信 就变得异常的 复杂和低效,为了解决这个问题,引入了 状态管理redux ) 从而很好的 解决多组件之间的 通信问题。

1.2、安装 Redux

redux 不是内嵌在 react 框架中,使用时需要手动去 安装

npm i -S redux      "redux": "^4.1.1",

1.3、三大原则

  • 单一 数据源

整个应用的 state 被储存在一棵对象结构中,并且这个 对象结构只存在于 唯一 一个 store 中

  • State 是 只读

redux 中的 state 只读 的 不可以直接修改

  • 使用纯函数 ( reducer ) 来执行修改 state

为了修改了 state 数据,redux 定义了一个 reducer 函数来完成 state 数据的 修改,reducer 会接收先前的 state 和 action,并返回 新的 state

操作原理图 :

 

、store ( 存储 ) 通过 reducer 创建了 初始状态

②、component ( 组件 ) 通过 store.getState( ) 方法获取 得到了 store 中保存的 state 数据 挂载在了自己的状态上

③、用户产生了操作,调用了 actions 的方法

④、actions 的方法被调用,创建了带有标示性信息的 action(描述对象)

、actions 将 action 通过 调用 store.dispatch 方法发送到了 reducer

、reducer 接收到 action 并根据标识信息判断之后返回了 新的 state

、store 的 state 被 reducer 更改为新 state 的时候,store.subscribe 方法里的 回调函数 会执行,此时就可以通知 component 去 重新获取 state

1.4、使用 redux

计数器 案例:

  • 创建统一的 数据源

在 src 目录下新建 store / index.js 作为 数据源 声明文件

// 常规导入
import { createStore } from "redux"

// 创建默认的 数据源(state)
const defaultState = {
    // 初始数据
    count: 0
}

// 负责处理数据
function reducers(state = defaultState, action) {
    // 返回新的 state 数据
    return state
}

// 创建仓库
const store = createStore(reducers)

// 导出
export default store
  • 组件中获取 / 设置数据
import React, { Component } from "react"
// 导入仓库
import store from './store'

class App extends Component {
    constructor(props){
        super(props)
        // 获取初始数据
        this.state = store.getState()
}
componentDidMount(){
        // 订阅数据(获取更新)
        store.subscribe(() => this.setState(state => store.getState()))
}
    render() {
        return (
            <div>
                { /* 渲染的内容 */ }
            </div>
        );
    }

    incr() {
        // 任务清单
        const actionCreator = {
            type: 'incr',
            payload: 1
        }
        // 派发任务清单
        store.dispatch(actionCreator);
    }
}

export default App;

  • 安装 Redux DevTools

为了方便调试 redux(可选安装),建议去谷歌商店安装 redux-devtools

 在使用的时候需要参考其说明页面,参考其使用说明给代码做对应的调整。

// 创建仓库
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

随后打开浏览器调试工具就可以看到类似如下界面:

1.5、代码模块化

现在我们已经能够很好的进行 redux 的 数据管理,但是有一个缺点就是所有的代码都写在一个文件中,需要按照 模块化开发 的规则 进行对 代码 拆分。

未拆分前的代码 : 

index.js

// redux 入口 , 仓库
// 常规导入
import { createStore } from 'redux'

// 创建默认的数据源 state
const initState = {
  // 初始数据
  count: 100
}

const mutation = {
  incr(state, data) {
    return {
      ...state,
      count: state.count + data
    }
  }
}

function reducers(state = initState, action) {
  console.log(action.type);
  try {
    return mutation[action.type](state, action.payload)
  } catch (error) {
    return state
  }
}

// 负责处理 state 状态的函数 , 这个函数一定要有返回值
/* function reducers(state = initState, action) {
  if ('incr' === action.type) {
    // 深复制 , 返回新的 state 对象
    // return {
    //   ...state,
    //   count: state.count + action.payload
    // }
    let newState = JSON.parse(JSON.stringify(state))
    newState.count = newState.count + action.payload
    return newState
  }
  // 返回新的 state 数据
  return state
} */

// 创建仓库
const store = createStore(
  reducers,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)

// 导出
export default store

App.jsx

import React, { Component } from 'react';
// 有一个 store 对象
import store from './store';
class App extends Component {
  // 私有状态
  state = store.getState()
  addNum = () => {
    // 修改 redux 中的 state 数据对象
    let actionCreate = {
      type: 'incr',
      payload: 10
    }
    // 组件通过 redux 来完成对于 state 数据的更新 -- 更新是同步操作
    store.dispatch(actionCreate)
    // 手动获取一次最新的数据
    this.setState(store.getState)
  }
  // 挂载完毕后 , 主动订阅 redux
  componentDidMount() {
    // 订阅
    // debugger
    this.unsubscribe = store.subscribe(() => this.setState(store.getState()))
  }
  componentWillUnmount() {
    // 取消订阅
    this.unsubscribe()
  }
  render() {
    return (
      <div>
        <h3>{this.state.count}</h3>
        <button onClick={this.addNum}>+++</button>
      </div>
    );
  }
}
export default App;

 模块化拆分后的代码 :

拆分 index.js 

 拆分 App.jsx


二、react-redux

网址:React Redux | React Redux

React-Redux 是 Redux 的官方针对 React 开发的 扩展库,默认没有在 React 项目中安装,需要手动来安装。react-redux 是依赖于 redux ,所以你必须安装 redux

你可以理解为 react-redux 就是 redux 给我们提供一些 高阶组件 ,能解决的问题是:使用它以后我们不需要在每个组件中再去手动  订阅数据的更新了。

安装指令 :  npm install -D redux-devtools-extension

// 让 redux 在 chrome( 谷歌浏览器 ) 中的 redux 插件中能 调试

import { composeWithDevTools } from 'redux-devtools-extension';

import { createStore } from 'redux'
// 让redux在chrome中redux插件中能调试
import { composeWithDevTools } from 'redux-devtools-extension';

// 初始值
const initState = {
  count: 100,
  films: []
}

// 操作 state 方法
const mutation = {
  incr(state, data) {
    return {
      ...state,
      count: state.count + data
    }
  }
}

const reducer = (state = initState, { type, data }) => {
  try {
    return mutation[type](state, data)
  } catch (error) {
    return state
  }
}

export default createStore(
  reducer,
  composeWithDevTools()
)

npm i -S react-redux  redux

  • 定义 Provider

在程序主文件 index.js 文件中, 定义 Provider

此处类似于之前 跨组件通信处的 Provider 一样,旨在让全局的组件 共享 store 中的数据

 

  • 在 子组件 中使用 react-redux

 

 

 react-redux 拆分 :


三、redux 中间件

3.1、介绍

redux 默认支持 同步操作,异步操作 怎么办?Action 发出以后,Reducer 立即算出 State ,这叫做同步;Action 发出以后,过一段时间再执行 Reducer ,这就是 异步 。怎么才能 Reducer 在 异步操作 结束后自动执行呢?这就要用到新的工具:中间件 。中间件有很多,这里使用一个 Redux 官方出品的 中间件库:redux-thunk

3.2、使用 redux-thunk

它支持函数返回,本质还是在 在内部调用 dispatch 返回一个固定值 对象

npm i -S redux-thunk

在 createStore 实例 store 中使用

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
    reducer,
    applyMiddleware(thunk)
)
export default store

注:需要注意,加上了以上代码后,redux 的调试工具就会失效并且会报错,如果还想使用 redux 的调试工具,则需要调整代码为
const composeEnhancers = (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
const store = createStore(
    reducer,
    composeEnhancers(applyMiddleware(thunk))
);


# action
export const incrCount = () => dispatch => {
  setTimeout(() => {
    dispatch({
      type: 'add',
      payload: 1
    })
  }, 1000)
}


四、Redux 模块化

Redux 提供了一个 combineReducers 方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。

ESLint  报的错误 , 只是告诉你在赋值之前最好不要直接导入

// eslint-disable-next-line

自动化导入 reducer :

// 参数1 : 扫描的目录
// 参数2 : 是否递归
// 参数3 : 返回什么样扩展名的文件
const moduleFn = require.context('../reducers', false, /\.js$/)
const modules = moduleFn.keys().reduce((pv, cv) => {
  let key = cv.match(/\.\/(\w+)\.js$/)[1]
  let value = moduleFn(cv).default
  pv[key] = value
  return pv
}, {})

五、路由

5.1、介绍

现代的前端应用大多数是 SPA( 单页应用程序 ),也就是只有一个 HTML 页面的应用程序。因为它的 用户体验更好、对服务器压力更小,所以更受欢迎。为了有效的使用 单个页面 来管理 多页面的功能,前端路由 应运而生。

  • 前端路由功能:让用户从一个视图 ( 组件 ) 导航到另一个视图 ( 组件 )
  • 前端路由是一套 映射 规则,在 React 中,是 URL 路径 与 组件 的对应关系
  • 使用 React 路由简单来说,就是配置 路径 和 组件

5.2、路由使用

React Router: Declarative Routing for React.js

5.2.1、安装路由模块

路由模块 不是 react 自带模块,需要安装第 3 方模块

npm i -S react-router-dom

5.2.2、相关组件

  • Router 组件:包裹整个应用,一个 React 应用只需要使用一次

   Router: HashRouter 和 BrowserRouter

       HashRouter: 使用 URL 的 哈希值 实现 ( localhost:3000/#/first )

       BrowserRouter:使用 H5 的 history API 实现( localhost:3000/first )

  • Link / NavLink 组件:用于指定 导航链接( a 标签 )

最终 Link 会编译成 a 标签,而 to 属性会被编译成 a 标签的 href 属性

  • Route 组件:指定路由 展示组件 相关信息( 组件渲染 )

path 属性:路由规则,这里需要跟 Link 组件里面 to 属性的值一致

   component 属性:展示的 组件

各组件关系示意图

定义项目使用路由,在入口文件 / src / index.js 文件中定义 路由模式 

 定义 路由规则 和 匹配成功 的 渲染组件

 在浏览器中输入后尝试 匹配

 

5.3、声明式导航

使用 Link 或 NavLink 组件完成 声明式导航 的 定义

Link / NavLink 区别

  • Link 组件不会根据 路由 的 变化 而 添加或修改 编译后 html 标签中的 属性
  • NavLink 会根据 路由 的 变化 而 自动修改 编译后 html 标签中的 属性

如果当前的 路由规则 和 Navlink 中的 To 所写的 规则一致 则 添加 class 样式,

默认名称为 active ,可以通过 activeClassName 来修改 匹配成功后 样式名称。

import React, { Component } from 'react'
import { HashRouter as Router, Route, Link } from 'react-router-dom'
import Home from './pages/Home'
import News from './pages/News'

class App extends Component {
  render() {
    return (
      <Router>
        <h3>导航区域</h3>
        <hr />
        <ul>
          <li>
            <Link to="/home">首页</Link>
          </li>
          <li>
            <NavLink to="/news">新闻</NavLink>
          </li>
        </ul>
        <hr />
        <Route path="/home" component={Home} />
        <Route path="/news" component={News} />
      </Router>
    );
  }
}
export default App

5.4、编程式导航

react-router-dom 中通过 history 对象中的 push / replace / go 等方法 实现 编程式导航 功能。

注:默认在 react 组件中,只能通过 路由匹配 成功后 直接 渲染组件才会有 路由对象,只有有 路由对象 ,才能 实现 编程式导航 。

5.5、路由参数

路由参数:在 Route 定义 渲染组件 时给定动态绑定的参数。

React 路由传参方式有三种:

  • 动态路由参数( param )

   以 “/detail/:id” 形式传递的数据

   在 落地组件 中通过 this.props.match.params 得到

  • 查询字符串(search)

   通过地址栏中的 ?key=value&key=value 传递

   在落地组件中通过 this.props.location.search 得到

  • 隐式传参(state),通过 地址栏 是观察不到的

   通过 路由对象 中的 state 属性进行 数据传递

   在落地组件中通过 this.props.location.state 得到

5.6、嵌套路由

在有一些功能中,往往 请求地址 的 前缀 是相同的,不同的只是后面一部份,此时就可以使用 多级路由( 路由嵌套 )来实现此路由的 定义实现 。

例如,路由规则 如下

admin / index

admin / user

它们路由前缀的 admin 是相同的,不同的只是后面一部份。

实现方式

  • 先需要定义个组件,用于负责匹配 同一前缀 的 路由,将匹配到的路由指向到 具体的模块

<Route path="/admin" component={Admin}></Route>

  • 创建模块路由组件负责指定各个路由的 去向

5.7、三种路由渲染方式

5.7.1、component (组件对象或函数)

5.7.2、render (函数)

<Route path="/home" render={router=><Home {router} />} />

 

5.7.3、children (函数或组件) 

5.8、withRouter 高阶组件

作用:把不是通过路由直接渲染出来的组件,将 react-router 的 history、location、match 三个对象传入 props 对象上

默认情况下必须是经过路由匹配渲染的组件才存在 this.props ,才拥有路由参数,才能使用 编程式导航 的写法,执行 this.props.history.push('/uri') 跳转到对应路由的页面,然而不是所有组件都直接与路由相连的,当这些组件需要 路由参数 时,使用 withRouter 就可以给此组件传入路由参数,此时就可以使用 this.props

 

5.9、自定义导航组件

为何需要 自定义导航 ?

因为在项目中往往不是所有的 声明式导航 都是需要 a 标签完成,有时候可能需要别的标签,此时如果在需要的地方去写 编程式导航 就会有代码重复可能性,就在对于公共代码进行提取。

思路:

  • 定义一个 普通组件 可以是 类组件 也可以是 函数式组件
  • 父组件 能向 子组件 传值  props
  • 此 高阶组件 不管 路由规则 是否匹配都要有渲染  children


六、过渡动画组件

6.1、给组件使用过渡动画

npm i -S react-transition-group

6.2、列表过渡

import React, { Component } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import './app.css'

class App extends Component {
  state = {
    todos: []
  }
  render() {
    return (
      <div>
        <TransitionGroup>
          {/* 使用了 TransitionGroup,这样的列表过渡动画,
		  则 CSSTransition 中原本的 in属性不要用了,取而代之为 key, key唯一不重复的 */}
          {
            this.state.todos.map((item, index) => (
              <CSSTransition
                // 唯一的
                key={item.id}
                // 动画时长 毫秒
                timeout={600}
                // string|object 样式前缀名称|自定义样式名称
                classNames="wu"
                // 动画结束后删除对应的dom
                unmountOnExit
              >
                <div>
                  <span>{index + 1}</span>
                  <span>{item.title}</span>
                  <span onClick={() => {
                    // 注意要用回调函数的方案,为了数据完整性
                    this.setState(state => {
                      // 当有获取原数据时,建议全用回调函数
                      let todos = state.todos
                      todos.splice(index, 1)
                      return {
                        todos
                      }
                    })
                  }}>删除</span>
                </div>
              </CSSTransition>
            ))
          }
        </TransitionGroup>

        <button onClick={() => {
          this.setState(state => ({
            todos: [...state.todos, {
              id: Date.now(),
              title: 'abc--  ' + Date.now()
            }]
          }))
        }}>添加任务</button>
      </div>
    );
  }
}

export default App;

6.3、利用高阶组件给组件添加动画

并不想让所有的路由都有动画效果,只是想对指定的页面有路由切换效果,可以利用 高阶组件 来完成。让组件有动画

# 定义 高阶组件
import React, { Component } from 'react'
import { CSSTransition } from 'react-transition-group'
//import '../assets/animate.css'

const withAnimation = Cmp => {
  return class extends Component {
    render() {
      return (
        <CSSTransition
          in={this.props.match !== null}
          timeout={600}
          classNames={{
            enter: 'animate__animated',
            enterActive: 'animate__fadeIn',
            exit: 'animate__animated',
            exitActive: 'animate__fadeOut'
          }}
          unmountOnExit
        >
          <Cmp {...this.props} />
        </CSSTransition>
      )
    }
  }
}

export default withAnimation

# 使用
@withAnimation
class Page extends Component {
    render() {
      return <div>高阶组件完成路由切换动画效果</div>
    }
}

// 使用高阶组件定义路由动画组件是一定要用它的 Route children 来渲染组件
<Route path="/home" children={props => <Page {...props} />} />

6.4、集合 animate.css

网址:Animate.css | A cross-browser library of CSS animations.

npm install animate.css --save


七、immutable.js

7.1、概述

官网:Immutable.js

List (List) - Immutable 3.8.1 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云

Immutable.js 出自 Facebook ,是最流行的 不可变数据结构 的实现之一。它实现了完全的持久化数据结构,使用结构共享。所有的更新操作都会返回新的值,但是在内部结构是共享的,来减少内存占用 ( 和垃圾回收的失效 )。

持久化数据结构:这里说的 持久化 是用来描述一种 数据结构,指一个数据,在被修改时,仍然能够保持 修改前的状态,即 不可变类型。

结构共享:Immutable 使用先进的 tries (字典树) 技术实现结构共享来解决性能问题,当我们对一个 Immutable 对象进行操作的时候,ImmutableJS 会只 clone 该节点以及它的祖先节点,其他保持不变,这样可以共享相同的部分,大大提高性能。

惰性操作:创建的对象时其实代码块没有被执行,只是被声明了,代码在获取或修改的时候才会实际被执行

7.2、使用 immutable 优缺点

7.3、安装

npm i -S immutable

7.4、常用 Api

fromJS => 递归 : 自动把数组 或 对象转为 Map 或 List


Map 是 immutable 中的 对象 ,不是原生 js 中的 对象

数据获取  get ( 单层级 获取 ) getIn ( 多层级 获取数据 ) 

is  =>  专门用来  比对数据

数据修改 =>  set / setIn (深层修改) / update / updateIn (深层修改)

// set 设置值,不需要原值,update 可以保留原值 的基础上来进行修改

// toJS 方法,就是把 immutable 对象转为原生 js 对象

合并对象  = >  // 如果两者有相同字段,则以合并中的方法字段为准

// 删除  istate.removeIn(['键名'])


List 类型  对应数组

// List  把 js 中的数组转为 List     它只能转换数组的第1层

// 获取 get / getIn

// 追加 push / unshift / pop / shift / concat

// 修改  set / update

// immutable 中的 List 长度  =>  istate.size

// 删除  istate.remove( 索引 )

// immutable 结构数据共享
// Map   把 js 中的 json 对象转为 Map  它只能转换 json 对象的第1层
// List  把 js 中的数组转为 List     它只能转换数组的第1层
// fromJS 递归 自动把数组 或 对象转为 Map 或 List
// is 专门用来 比对数据
// eslint-disable-next-line
import { Map, List, fromJS, is } from 'immutable'

let obj = { id: 1, name: '张三', user: { age: 20 } }
// 把对象转为 Map Map 它是 immutable 中的对象,不是原生 js 中的对象
// let istate = Map(obj)
let istate = fromJS(obj)
// console.log(obj,istate);

// 获取数据 get / getIn
/* console.log('get', istate.get('name'));
console.log('getIn', istate.getIn(['name'])); */
console.log('get', istate.get('user').get('age'))
console.log('getIn', istate.getIn(['user', 'age']));

// 对象比对
// let obj1 = { id: 1, name: '张三', user: { age: 20 } }
// let obj2 = { id: 1, name: '张三', user: { age: 20 } }
// console.log(obj1 === obj2) // false  但是它们值是一样的
let obj = { id: 1, name: '张三', user: { age: 20 } }
let istate1 = fromJS(obj)
let istate2 = fromJS(obj)
console.log(is(istate1, istate2))    //  true

// 修改 -- 任何的修改都会返回一个新的对象
let obj = { id: 1, name: '张三', user: { age: 20 } }
let istate = fromJS(obj)
// 修改 set / setIn / update / updateIn
// set 设置值,不需要原值,update 可以保留原值的基础来进行修改
// let newState = istate.set('name','李四')
// let newState = istate.update('name', v => '李四' + v)
// 深层数据修改
// let newState = istate.setIn(['user', 'age'], 30)
// let newState = istate.updateIn(['user', 'age'], v => v + 1)
// toJS 方法,就是把 immutable 对象转为原生 js 对象
console.log(newState.toJS());

// 删除
let obj = { id: 1, name: '张三', user: { age: 20 } }
// 普通对象删除属性    // delete obj['name']
let istate = fromJS(obj)
let newState = istate.removeIn(['name'])
console.log(newState.toJS());

// 合并对象
let obj1 = { id: 1, name: '张三' }
let obj2 = { name: '李四', age: 20 }
// for in  ...   Object.assign
let istate1 = fromJS(obj1)
let istate2 = fromJS(obj2)
// 如果两者有相同字段,则以合并中的方法字段为准
let newState = istate1.merge(istate2)
console.log(newState.toJS());

// --------------------------------------------------------
// List 类型  对应数组
let arr = [1, 2, { id: 1, name: '张三' }]
// // let istate = List(arr)
let istate = fromJS(arr)

// 获取 get / getIn
// console.log(istate.get(1));    //  2
console.log(istate.getIn([2, 'name']));    // 张三

// 追加 push / unshift / pop / shift / concat
let newState = istate.push(3)
console.log(newState.toJS());

// 修改  set / update
// let newState = istate.set(1, 10)
// let newState = istate.update(1, v => v + 10)
let newState = istate.updateIn([2,'id'], v => v + 10)
console.log(newState.toJS());

// immutable 中的 List 长度
console.log(istate.size);

// 删除
let newState = istate.remove(1)
console.log(newState.toJS());

7.5、redux 中集成 immutable

  • 安装 redux-immutable

redux 中利用 combineReducers 来合并 reducer 并初始化 state,redux 自带的 combineReducers只支持 state 是原生 js 形式的,所以需要使用 redux-immutable 提供的 combineReducers 来替换原来的方法。

npm i -S redux-immutable

  • 使用

它的合并是支持 immutable 对象合并

import { combineReducers } from 'redux-immutable'

把所有的 reducer 数据转换为 immutable 对象

import {fromJS} from 'immutable'

const defaultState = fromJS({})


图例 :


跳转链接 =>  React_01 学习笔记

跳转链接 =>  React_02 学习笔记

跳转链接 =>  React_04 菜谱项目

跳转链接 =>  React_05 Hooks

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值