useRef

  • 官方文档

  • 我们可以认为函数组件中的useRef就是类组件中的createRef,都是用于保存引用值,指向和操作DOM元素对象

// 08-useRef 相当于 类组件中的createRef

import React,{useRef,useState} from 'react'

export default function App() {
const myText = useRef('初始默认值')
const [content,setContent] = useState('')
return (
<div>
{/* 通过useRef设置ref属性 */}
<input ref={myText}/>

<button onClick={()=>{
// 通过 ref 属性获取到输入框的数据
console.log('输入框的数据库为:',myText.current.value)
setContent(myText.current.value)//将数据更新到state中
myText.current.value = ''//清空输入框!
}}>add</button>

<p>state状态:{content}</p>
</div>
)
}
  • 结果展示:

image

useContext

  • 官方文档

  • 函数式组件中的useContext我们可以认为是类组件中的Context,都是用于跨组件间通信的,同时可将其类比为vue中的事件总线,它设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。使用 context, 我们可以避免通过中间元素传递 props。

  • 在函数式组件中使用useContext实现跨级组件间的通信同样要结合createContext来使用,首先使用createContext来创建全局事件总线,随后使用.Provider来指定消息的提供方, 再在其他组件中使用useContext()来获取消息提供方的数据,最后直接调用即可,不需要使用.Consumer来包裹调用!

代码展示;

// useContext

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

// 1. 创建全局事件context
const myData = createContext(0)

// 孙子组件
const Sun = () => {
// 使用useContext接收父组件提供的数据(类组件则需要使用.Consumer)
var {num,addNum} = useContext(myData)
console.log(useContext(myData));
return (
<div>
<p>---------------- 孙组件 ------------------</p>
{/* 直接使用结构出来的数据和方法即可 */}
<p>父组件中获取到的数据;{num}</p>
<button onClick={addNum}>点击父组件中的数据+1</button>
</div>
)
}

// 子组件(无关紧要)
const Son = () => {
return (
<div>
<p>---------------- 子组件 ------------------</p>
<p>作为中间人将孙组件和父组件间隔为跨级组件</p>
<Sun></Sun>
</div>
)
}

// 父组件
export default function Father() {
const [num,setnum] = useState(0)//num数据
const addNum = ()=>{
setnum(num+1) //数据+1的方法
}
return (
// 使用.Provider将父组件包装成数据的提供方
<myData.Provider value={{num,addNum}}>
<div>
<p>---------------- 父组件 ------------------</p>
<p>state中的数据:{num}</p>
<Son></Son>
</div>
</myData.Provider>
)
}

结果展示:

image

useReducer

  • 官方文档

  • useReduceruseState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。对比之下,useState是将状态保存到组件内部,而useReducer则是将状态提取到所有组件之外共同管理,在结合useContext的使用后基本可以作为Redux来使用,就类似于Vue中的Vuex

Reducer

  • 为了更好的理解useReducer,所以先要了解JavaScript里的Redcuer是什么。它的兴起是从Redux广泛使用开始的,但不仅仅存在Redux中,可以使用原生的JavaScript来完成Reducer操作。所谓reducer其实就是一个函数,这个函数接收两个参数,一个是状态,一个用来控制业务逻辑的判断参数。我们举一个最简单的例子。
// 接收指定的字段做指定的数据操作
function countReducer(state, action) {
switch(action.type) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}

上面的代码就是Reducer,你主要理解的就是这种形式和两个参数的作用,一个参数是状态,一个参数是如何控制状态。

useReducer结合useContext实现类似redux

  • 案例需求: 创建全局变量(state)和修改全局变量的唯一方法(reducer), 创建一个全局的父组件(相当于项目中的app组件),在这个父组件管辖下有三个子组件,第一个子组件用于展示数据的修改按钮,第二和第三个子组件则用于展示数据。也就是说这个Demo中有两个按钮,点击第按钮能够修改全局状态中对应的数据。
// useReducer结合useContext来使用(Redux,Vuex,深层级组件调用共同状态)
/*
语法解析: const [state, dispatch] = useReducer(reducer, initailState)
由上面的语句可以看出,创建[state,dispatch] 且提供reducer和initailState作为形参
随后在App组件中注册全局的state和统一修改state的方法(dispatch),App组件再通过
context将全局的state和修改方法流通给其他组件,其他组件再通过dispatch这个方法里面
传入(对象)指定type字段来调用修改全局属性(state)的方法修改全局属性,切记在修改全局属性
的方法中,最好只做变量赋值,不要直接修改全局属性。的这其实就非常的类似Vuex或Redux了
*/
import React,{useReducer,useContext} from 'react'

// 1. 创建所有组件以外的初始状态
const initailState = {
name:"lam",
age:19
}

// 2. 创建修改状态的方法(推荐只做赋值,不能直接操作原状态)
/*
preState: 前一次计算完成后的状态;
action: 通过 dispatch 调用传递过来的参数;
*/
const reducer = (prevState,action)=>{
console.log('旧状态',prevState);
console.log('对应的处理字段以及新的state参数',action);

// 尽量不要操作状态(必须保留原状态),使用变量互换替代
let newstate = {...prevState}
// 捕获修改字段,做对应的状态覆盖
switch(action.type){
case "changeName":
newstate.name = action.value
return newstate //返回新状态
case "addAge":
newstate.age = action.value
return newstate //返回新状态
default:
return prevState //返回老状态
}
// return prevState
}

// 创建全局context事件总线
const GlobalContext = React.createContext()

// 父组件(用于注册全局状态和修改全局状态的方法,并以Context的方式将这状态和方法传给后面的组件)
export default function App() {
// 3. 在项目中所有子组件的共同父组件中注册全局state以及唯一的修改方法
const [state, dispatch] = useReducer(reducer, initailState)
console.log('全局状态',state);

return (
// 4. 使用context使父组件为所有的子组件提供数据状态,以及修改数据状态的方法
<GlobalContext.Provider value={{state,dispatch}}>
<div>
<Child1/>
<Child2/>
<Child3/>
</div>
</GlobalContext.Provider>
)
}

// 子组件1(修改全局状态)
function Child1(){
// 5. 子组件通过useContext获取修改全局状态的方法和全局状态
const {dispatch,state} = useContext(GlobalContext)
console.log('当前年龄为',state.age);

return <div style={{background:"pink"}}>
<button onClick={()=>{
/*
6. 子组件使用dispatch方法修改全局状态,dispatch传入一个对象,
里面必须要有唯一字段用于指定后续的状态覆盖,以及新的状态数据
*/
dispatch({
type:"changeName",//指定唯一字段必传项
value:"肥林",//新的状态数据
})
}}>修改姓名</button>

<button onClick={()=>{
dispatch({
type:"addAge",
value:state.age+1
})
}}>+年龄</button>
</div>
}

// 子组件2(展示全局状态)
function Child2(){
// 通过useContext获取修改全局状态的方法
const {state} = useContext(GlobalContext)
return <div style={{background:"skyblue"}}>
{/* 7. 子组件通过state来获取全局状态数据并展示 */}
姓名-{state.name}
</div>
}

// 子组件3(展示全局状态)
function Child3(){
const {state} = useContext(GlobalContext)
return <div style={{background:"yellowgreen"}}>
年龄-{state.age}
</div>
}

结果展示:

image

自定义hooks

  • 官方文档

  • 所谓自定义hooks我们可以简单的认为就是函数的封装,但是这个函数的名字必须以use为开头,否则就不能使用React中的各种hooks,这个函数(你自定义的hooks)在形式上和普通函数没有区别,你可以传递任意参数给这个Hooks。但是要注意,Hooks和普通函数在语义化上是由区别的,就在于函数没有用到其他Hooks。什么意思呢?就是说如果你创建了一个 useXXX 的函数,但是内部并没有用任何其它 Hooks,那么这个函数就不是一个 Hook,而只是一个普通的函数。但是如果用了其它 Hooks ,那么它就是一个 Hook。

案例展示:

  • 我自定义一个计算机hooks,里面有加法,减法,复位以及输出结果
// 自定义hooks
import React,{useState, useCallback} from 'react'

// 自定义hooks(其实就是函数的封装)
function useCounter() {
/*
在这个函数的内部可以使用到React提供的各种hooks
*/

// 定义 count 这个 state 用于保存当前数值
const [count, setCount] = useState(0);
// 实现加 1 的操作
const increment = useCallback(() => setCount(count + 1), [count]);
// 实现减 1 的操作
const decrement = useCallback(() => setCount(count - 1), [count]);
// 重置计数器
const reset = useCallback(() => setCount(0), []);

// 将业务逻辑的操作 export 出去供调用者使用
return { count, increment, decrement, reset };
}

export default function App() {
// 调用我们自定义的Hook
const { count, increment, decrement, reset } = useCounter();
return (
<div>
<p>结果: {count}</p>
<button onClick={increment}> +1 </button>
<button onClick={decrement}> -1 </button>
<button onClick={reset}> 复位 </button>
</div>
)
}

结果展示:

image