不公天道,吾自立身,不朽不名,八荒春风,春风已至,不见静春,天下有我齐静春,天下快哉,我亦快哉。---------《剑来》齐静春
1.受控绑定
import { useRef, useState } from "react";
/** *受控绑定通过react状态控制input框的状态
* 1,声明一个react状态-useState
* * **********
* 2、核心绑定流程
*
* 1,通过value属性绑定react状恋
* 2、绑定onChange事件通过事件参数e拿到输入框最新的值 反向修改到react状态
* */
const Formbind = () => {
// input绑定值
const [value, setValue] = useState("");
return (
<>
<h3>表单受控绑定</h3>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</>
);
};
export default Formbind;
2.获取DOM
import { useRef, useState } from "react";
/** React中获取D0N****
* 1.useRef生成ref对象 绑定到dom标签身上
* 2.dom可用时,ref.current获取dom
* 渲染完毕后,dom生产之后才可用
* */
const Formbind = () => {
// input绑定值
const [value, setValue] = useState("");
// 定义input的ref
const inputRef = useRef(null);
// 获取input的dom
const handleGetDom = () => {
console.log(inputRef.current);
console.dir(inputRef.current);
};
return (
<>
<h3>表单受控绑定</h3>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
ref={inputRef}
/>
<h3>获取表单dom</h3>
<button onClick={handleGetDom}>获取DOM</button>
</>
);
};
export default Formbind;
3.组件通信
1.父传子
// 父传子
//1.父组件传递数据 子组件标签身上绑定属性
//2.子组件接收数据 props的参数
/**
* 1.props可传递任意的数据数字、字符串、布尔值、数组、对象、函数、JSX
* 2.props是只读对象
* 子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改
*父传子-特殊的prop children
场景:当我们把内容嵌套在子组件标签中时,父组件会自动在名为children的prop属性中接收该内容
* */
import { useState ,useContext } from "react";
//子组件
function Son(props) {
// props:对象里面包含了父组件传递过来的所有的数据
console.log("props", props);
// Cannot assign to read only property 'name' of object '
//props.name = "newName"; 错误写法 props是只读的会报错
return (
<div>
子组件名字:{props.name},jsx:{props.child},children:{props.children}
</div>
);
}
//父组件
function ComCommunication() {
// 父传子
const name = "this is app";
return (
<>
<h3>父子通信</h3>
<h4>父传子参数</h4>
<Son
name={name}
age={18}
isTrue={true}
list={["vue", "react"]}
obj={{ name: "jack" }}
fun={() => console.log(123)}
child={<span>this is span</span>}
/>
<h4>特殊父传子</h4>
<Son>
<span>第二个组件</span>
</Son>
</>
);
}
export default ComCommunication;
2.子传父
//核心:在子组件中调用父组件中的函致并传递实参
import { useState ,useContext } from "react";
//核心:在子组件中调用父组件中的函致并传递实参
function Son2({ onGetMsg }) {
const sonMsg = "Son2";
return (
<div>
<div>我是Son2组件</div>
<button onClick={() => onGetMsg(sonMsg)}>子传父按钮</button>
</div>
);
}
function ComCommunication() {
// 定义接收子msg的状态
const [msg, setMsg] = useState("");
// 接受子msg的函数
const getMsg = (msg) => {
console.log("msg", msg);
setMsg(msg);
};
return (
<>
<h4>子传父参数</h4>
<Son2 onGetMsg={getMsg}></Son2>
<div>接收到son2的name:{msg}</div>
</>
);
}
export default ComCommunication;
3.兄弟传参(状态提升)
/**
* 实现思路:借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递
*1. A组件先通过子传父的方式把数据传给父组件App
*2. App拿到数据后通过父传子的方式再传递给B组件
*/
import { useState ,useContext } from "react";
// 兄弟传参
/**状态提升机制 */
// 1.通过子传父 A -> ComCommunication
//2、通过父传子ComCommunication->B
function A({ onBGetMsg }) {
const bMsg = "我是你的哥哥A";
return (
<div>
我是A组件
<button onClick={() => onBGetMsg(bMsg)}>我要给弟弟B发消息</button>
</div>
);
}
function B({ msg }) {
return <div>我是B组件,A传给我的msg:{msg}</div>;
}
function ComCommunication() {
// 兄弟传参
const [bmsg, setBmsg] = useState("");
// 从A获取msg
const getBmsg = (msg) => {
setBmsg(msg);
};
return (
<>
<h4>兄弟传参(状态提升)</h4>
<A onBGetMsg={getBmsg}></A>
<B msg={bmsg}></B>
</>
);
}
export default ComCommunication;
4.跨层通信(Context机制)
/**
*
* 实现步骤:
*1.使用createContext方法创建-一个上下文对象Ctx
*2.在顶层组件(App)中通过Ctx.Provider 组件提供数据
*3.在底层组件(B)中通过useContext钩子函数获取消费数据
*
*/
import { useState ,useContext } from "react";
import { createContext } from "react";
// //App->A->B
// 1. createContext方法创建一个上下文对象
// 2.在顶层组件通过Provider组件提供数据
// 3.在底层组件通过useContext钩子函数使用数据
const MsgContext = createContext();
function C() {
return (
<div>
我是组件C
<D></D>
</div>
);
}
function D() {
const msg = useContext(MsgContext);
return <div>我是组件D,我收到了ComCommunication的信息:{msg}</div>;
}
function ComCommunication() {
const name = "this is app";
return (
<>
<h4>跨层通信(Context机制)</h4>
<MsgContext.Provider value={name}>
<C></C>
</MsgContext.Provider>
</>
);
}
export default ComCommunication;
4.useEffect函数的使用
/**
* useEffect的概念理解
* useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送AJAX请求,更改DOM等等
* useEffect(()=>{},[])
* 参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作
* 参数2是一一个数组(可选参) , 在数组里放置依赖项,不同依赖项会影响第-个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次
*
*/
1.发送请求示例
import { useEffect, useState } from "react";
function UseEffectMethods() {
// 接口地址
const URL = "http://geek.itheima.net/v1_0/channels";
// 创建存储状态
const [efList, setEfList] = useState([]);
// 发送请求
useEffect(() => {
async function getList() {
let res = await fetch(URL);
const JsonRes = await res.json();
setEfList(JsonRes.data.channels);
}
getList();
}, []);
return (
<div>
<h3>useEffect的使用</h3>
<h4>接口调用</h4>
{efList.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</div>
);
}
export default UseEffectMethods;
2.不同依赖项,函数执行影响
函数 | 依赖项 | 执行时机 |
useEffect(() => { console.log("1"); }); | 没有第二个参数 | 初始化 + 任意状态改变 |
useEffect(() => { console.log("1"); },[]); | [] | 初始化 |
useEffect(() => { console.log("1"); },[count]); | [count] | 初始化 + count状态改变 |
测试代码如下
import { useEffect, useState } from "react";
function TestUseEffect() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
// 运行时机 初始化 + 状态改变
/* useEffect(() => {
console.log("TestUseEffect组件useEffect运行初始化 + 状态改变");
}); */
// 运行时机 初始化
/* useEffect(() => {
console.log("TestUseEffect组件useEffect运行初始化");
}, []); */
// 运行时机 初始化 + 特定状态改变
useEffect(() => {
console.log("TestUseEffect组件useEffect运行初始化 + 特定状态改变");
}, [count2]);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<button onClick={() => setCount2(count2 + 1)}>{count2}</button>
</div>
);
}
function UseEffectMethods() {
return (
<div>
<h3>useEffect的使用</h3>
<h4>测试useEffect依赖不一样用法</h4>
<TestUseEffect></TestUseEffect>
</div>
);
}
export default UseEffectMethods;
3.清除副作用
/**
* useEffect-清除副作用
* useEffect(()=>{
* //实现副作用逻辑操作
* return()=>{
* //清除副作用操作
* }
* },[])
* 在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用
* 说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行
*
*
*/
import { useEffect, useState } from "react";
// useEffect-清除副作用
function RemoveSideEffect() {
useEffect(() => {
let timer = setInterval(() => {
console.log("我是RemoveSideEffect的定时器,运行中....");
}, 1000);
//如果不清除副作用则就算该组件不在页面中,则该定时器依旧在运行
return () => {
clearInterval(timer);
};
}, []);
return <div>我是组件RemoveSideEffect</div>;
}
function UseEffectMethods() {
const [show, setShow] = useState(true);
return (
<div>
<h3>useEffect的使用</h3>
<h4>清除副作用</h4>
{show && <RemoveSideEffect></RemoveSideEffect>}
<button onClick={() => setShow(false)}>卸载组件RemoveSideEffect</button>
</div>
);
}
export default UseEffectMethods;
5.自定义HOOK封装
//封装自定义hook通用思路
// 1.声明一个以use打头的函数
// 2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)
//3.把组件中用到的状态或者回调return出去(以对象或者数组)
//4.在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
// 封装自定义hook
//问题:布尔切换的逻辑 当前组件耦合在一起的 不方便复用
// 解决思路 自定义hook
import { useState } from "react";
// 1.声明use打头的函数
function useToggle() {
// 2.可复用逻辑代码
const [value, setValue] = useState(true);
const toggle = () => {
setValue(false);
};
//3.return出 需要在其他组件中使用 状态和回调函数
return {
value,
toggle,
};
}
function EncapsulationHook() {
//4.执行函数,解构出来状态和回调进行使用
const { value, toggle } = useToggle();
return (
<div>
<h3>自定义hook</h3>
{value && <div>this is div</div>}
<button onClick={toggle}>toggle</button>
</div>
);
}
export default EncapsulationHook;
6.useMemo
用于消耗非常大的计算,具有缓存的作用,跟vue的computed相似(主要用来缓存值)
import { useMemo, useState } from 'react'
// 缓存:消耗非常大的计算的时候
// 定义斐波那契数列求和
const fibonacci = (n) => {
console.log('计算函数执行了', n)
if (n < 3) return 1
return fibonacci(n - 1) + fibonacci(n - 2)
}
const UseMemo = () => {
console.log('组件重新渲染了')
const [count1, setCount1] = useState(0)
//useMemo包裹后,count2改变的时候斐波那契数列求和函数就不会重新执行,有缓存的作用,只有count1改变的时候,才会执行计算函数
const result = useMemo(() => fibonacci(count1), [count1])
const [count2, setCount2] = useState(0)
return (
<div>
<h3>useMemo的使用(计算属性)</h3>
<button onClick={() => setCount1(count1 + 1)}>
changeCount1:{count1}
</button>
<button onClick={() => setCount2(count2 + 1)}>
changeCount2:{count2}
</button>
{result}
</div>
)
}
export default UseMemo
7.React.Memo
使用 memo
将组件包装起来,以获得该组件的一个 记忆化 版本
React 通常在其父组件重新渲染时重新渲染一个组件。你可以使用 memo
创建一个组件,当它的父组件重新渲染时,只要它的新 props 与旧 props 相同时,React 就不会重新渲染它。这样的组件被称为 记忆化的(memoized)组件。
1.默认的渲染机制是子跟着父一起渲染
2.memo进行缓存只有props发生变化的时候才会重新渲染(不包含context的情况,state改变的情况)
需要注意的是:
React.memo props比较机制
1.传递一个简单类型的prop----- prop变化时组件重新渲染
2.传递一个引用类型的prop----- 比较的是新值和旧值的引用是否相等 当父组件的函数重新执行时,实际上形成的是新的数组引用
3.解决问题2的情况使用useMemo函数包裹引用类型数据: 保证引用稳定 -> useMemo 组件渲染过程中缓存一个值
import { memo, useMemo, useState } from 'react'
const Son = memo(({ count, title }) => {
console.log('子组件渲染了', title)
return (
<div>
son{title}---{count}
</div>
)
})
const ReactMemo = () => {
const [count, setCount] = useState(0)
//1.传递一个简单类型的prop prop变化时组件重新渲染
const num = 100
// 2.传递一个引用类型的prop 比较的是新值和旧值的引用是否相等 当父组件的函数重新执行时,实际上形成的是新的数组引用
const arr = [1, 2, 3]
// 3.保证引用稳定 -> useMemo 组件渲染过程中缓存一个值
const arr2 = useMemo(() => [1, 2, 3], [])
return (
<div>
<h3>react.Memo的使用(缓存子组件,只要props发生变化才执行) </h3>
<button onClick={() => setCount(count + 1)}>changeCount:{count}</button>
<Son count={count} title={'A--'} />
<Son count={num} title={'B--'} />
<Son count={arr} title={'C--'} />
<Son count={arr2} title={'D--'} />
</div>
)
}
export default ReactMemo
运行结果如下:(图1 是初始化;图2 count1改变时运行结果)
1.使用 state 更新记忆化(memoized)组件
即使一个组件被记忆化了,当它自身的状态发生变化时,它仍然会重新渲染。memoization 只与从父组件传递给组件的 props 有关。
如果将 state 变量设置为其当前值,即使没有使用 memo
,React 也会跳过重新渲染组件。你仍然可能会看到额外地调用组件函数,但其结果将被丢弃。
const Son = memo(({ count, title }) => {
const [num, setNum] = useState(0)
//num2改变子组件是一定会渲染的
const [num2, setNum2] = useState(0)
console.log('子组件渲染了', title)
return (
<div>
son{title}---{count}
<br />
{/*设置为当前状态子组件不会渲染,这边再改变num2之后调num按钮是因为num2改变的时候num按钮重新渲染了,所以会执行一次*/}
<button onClick={() => setNum(num)}>num:{num}</button>
<button onClick={() => setNum2(num2 + 1)}>num2:{num2}</button>
</div>
)
})
2.使用 context 更新记忆化(memoized)组件
即使组件已被记忆化,当其使用的 context 发生变化时,它仍将重新渲染。记忆化只与从父组件传递给组件的 props 有关。
8.useReducer
useReducer
是一个 React Hook,它允许你向组件里面添加一个 reducer。
//const [state, dispatch] = useReducer(reducer, initialArg, init?)
//reducer:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
initialArg:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
可选参数 init:用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg。
import { useReducer } from 'react'
// 定义reducer函数
const reducer = (state, action) => {
switch (action.type) {
case 'DEC':
return state - 1
case 'INC':
return state + 1
case 'SET':
return action.payload
default:
return state
}
}
const UseReducer = () => {
// 使用useReducer
const [state, dispatch] = useReducer(reducer, 0)
return (
<div>
<h3>useReducer的使用</h3>
<button onClick={() => dispatch({ type: 'DEC' })}>-</button>
<span>{state}</span>
<button onClick={() => dispatch({ type: 'INC' })}>+</button>
<button onClick={() => dispatch({ type: 'SET', payload: 100 })}>
设置
</button>
</div>
)
}
export default UseReducer
1.immer插件
//1.下载
npm install immer use-immer
//2.引入
import { useImmer , useImmerReducer } from "use-immer";
//3.使用
const [age, setAge] = useImmer(20); //相当于useState
const [state, dispatch] = useImmerReducer(reducer, initialState); //相当于useRdeucer
9.useCallback
useCallback
是一个允许你在多次渲染中缓存函数的 React Hook。
import { memo, useState, useCallback } from 'react'
const Son = memo(({ onChange }) => {
console.log('子组件渲染了')
return (
<>
<input type='text' onChange={(e) => onChange(e.target.value)} />
</>
)
})
const UseCallback = () => {
const [count, setCount] = useState(0)
//不使用 useCallback 函数的话,在count发生改变的时候,会重新渲染子组件
const handleChange = useCallback((val) => {
console.log(val)
}, [])
return (
<div>
<h3>useCallback(缓存函数)</h3>
<Son onChange={handleChange}></Son>
<button onClick={() => setCount(count + 1)}>count{count}</button>
</div>
)
}
export default UseCallback
10.forwardRef&useImperativeHandle
1.forwardRef
允许组件使用 ref 将 DOM 节点暴露给父组件。
import { forwardRef, useImperativeHandle, useRef } from 'react'
const Son = forwardRef((props, ref) => {
return (
<>
<input type='text' ref={ref} />
</>
)
})
const ForwardRefanduseImperativeHandle = () => {
const ref = useRef(null)
// 获取子组件的dom,执行focus事件
const getDom = () => {
ref.current.focus()
}
return (
<div>
<h3>forwardRef与useImperativeHandle的使用</h3>
<Son ref={ref}></Son>
<button onClick={getDom}>聚焦</button>
</div>
)
}
export default ForwardRefanduseImperativeHandle
2.useImperativeHandle
useImperativeHandle
是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。
/**
* 如果使用useImperativeHandle函数暴露方法的时候那么子组件的ref需要自己在自定义绑定,否则获取不到ref,如果只获取dom,则只要forwardRef
*
*/
import { forwardRef, useImperativeHandle, useRef } from 'react'
const Son = forwardRef((props, ref) => {
// 定义dom的ref
const ref2 = useRef(null)
const ref3 = useRef(null)
const getFocus = () => {
console.log('222')
ref2.current.focus()
}
const getFocus2 = () => {
console.log('111111')
ref3.current.focus()
}
// 把方法暴露出去
useImperativeHandle(ref, () => {
return {
getFocus2,
getFocus
}
})
return (
<>
<input type='text' ref={ref3} />
<input type='text' ref={ref2} />
</>
)
})
const ForwardRefanduseImperativeHandle = () => {
const ref = useRef(null)
// 获取子组件的方法
const getMethod = () => {
ref.current.getFocus()
}
const getMethod2 = () => {
ref.current.getFocus2()
}
return (
<div>
<h3>forwardRef与useImperativeHandle的使用</h3>
<Son ref={ref}></Son>
<button onClick={getMethod}>子组件方法聚焦</button>
<button onClick={getMethod2}>子组件方法聚焦2</button>
</div>
)
}
export default ForwardRefanduseImperativeHandle
11.类组件用法
import { Component, useState } from 'react'
class ClassCounter extends Component {
//编写组件的逻辑代码
//1、状态变量2.事件回调3.UI(JSX)
//1,定义状态变量
state = {
count: 0
}
setCount = () => {
//修改状态变量
this.setState({
count: this.state.count + 1
})
}
// 声明周期函数
//组件渲染完毕执行一次发送网络请求
componentDidMount() {
//组件挂载完成
console.log('组件挂载完成')
this.timer = setInterval(() => {
console.log('定时器...')
this.setState({
count: this.state.count + 1
})
}, 1000)
}
// 组件卸载的时候自动执行 副作用清理的工作 清除定时器 清除事件绑定
componentWillUnmount() {
//组件卸载
console.log('组件卸载')
clearInterval(this.timer)
}
render() {
return (
<>
<button onClick={this.setCount}>加{this.state.count}</button>
</>
)
}
}
class Son extends Component {
state = {
num: 100
}
render() {
return (
<div>
<h4>父传子</h4>
我是子组件{this.props.msg}
<h4>子传父</h4>
<button onClick={() => this.props.onGetNum(this.state.num)}>
子传父
</button>
</div>
)
}
}
function Counter() {
const [show, setShow] = useState(true)
const getNum = (val) => {
console.log('子组件传回来的', val)
}
return (
<div>
<h3>class类组件</h3>
<button onClick={() => setShow(false)}>卸载组件</button>
{show && <ClassCounter></ClassCounter>}
<Son msg='子组件 我是父组件呀.......' onGetNum={getNum} />
</div>
)
}
export default Counter
/**
* 1.父传子直接通过prop子组件标签身上绑定父组件中的数据即可// 2.子传父在子组件标签身上绑定父组件中的的数,子组件中调用这个的数传递参数
总结
*1.思想保持一致
* 2.类组件依赖于this
*
*/