useRedux践行Flux/Redux思想
使用方法:
- 将数据集中在一个 store 对象
- 将所有操作集中在 reducer
- 创建一个 Context
- 创建对数据的读取api
- 将第四步的内容放到第三步的 Context
- 用 Context.Provider 将 Context 提供给所有组件
- 各个组件用 useContext 获取读写API
useContext
全局变量是全局的上下文;上下文是局部的全局变量
使用方法:
- 使用 C = createContext(initial) 创建上下文
- 使用 <C.Provider> 圈定作用域
- 在作用域内使用 useContext(C)来使用上下文
父子孙 需要公用一个地方定义的C = createContext(initial)
test.js文件
import React, { useContext } from "react";
import Home from '@/components/Home';
export const TestContext= React.createContext({});
const Navbar = () => {
const { username } = useContext(TestContext)
return (
<div className="navbar">
<p>{username}</p>
</div>
)
}
const Messages = () => {
const { username } = useContext(TestContext)
return (
<div className="messages">
<p>1 message for {username}</p>
</div>
)
}
export default class testUseContext extends React.Component {
render() {
let name ="小人头"
return (
<TestContext.Provider value={{ username: 'superawesome'}}>
<div className="test">
<Navbar />
<Messages />
<Home />
</div>
</TestContext.Provider>
);
}
}
import React, { useContext } from "react";
import { TestContext } from "../pages/device-manage/test";
const Home = () => {
const { username } = useContext(TestContext)
return (
<div className="navbar">
<p>Home{username}</p>
</div>
)
}
export default Home;
useEffect
可以直接在此函数中处理生命周期事件,可以看作是componentDidMount、componentDidUpdate、componentDidUnMount这三个生命周期的组合。
如果同时存在多个useEffect,会按照出现次序执行;
不传递第二个参数会导致每次渲染都会运行useEffect;
function Demo() {
useEffect(() => {
// 默认情况下,每次渲染后都会调用该函数
console.log('render!');
// 如果要实现 componentWillUnmount,
// 在末尾处返回一个函数
// React 在该函数组件卸载前调用该方法
// 其命名为 cleanup 是为了表明此函数的目的,
// 但其实也可以返回一个箭头函数或者给起一个别的名字。
return function cleanup () {
console.log('unmounting...');
}
})
return "hello";
}
effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。这实际上比componentWillUnmount生命周期更强大,因为如果需要的话,它允许咱们在每次渲染之前和之后执行副作用。
不完全的生命周期
useEffect在每次渲染后运行(默认情况下),并且可以选择在再次运行之前自行清理。
与其将useEffect看作一个函数来完成3个独立生命周期的工作,不如将它简单地看作是在渲染之后执行副作用的一种方式,包括在每次渲染之前和卸载之前咱们希望执行的需要清理的东西。
阻止每次重新渲染都会执行 useEffect
如果希望 effect 较少运行,可以提供第二个参数 - 值数组。 将它们视为该effect的依赖关系。 如果其中一个依赖项自上次更改后,effect将再次运行。但如果数组中包含多个,只有一个变化,effect都会执行,但是如果不在数组中包含的值在useEffect函数中使用,此值保持原来的。
const [value, setValue] = useState('5');
useEffect(() => {
// 仅在 value 更改时更新
console.log(value);
}, [value])
仅在挂载和卸载的时候执行
可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。
useEffect(() => {
console.log('mounted');
return () => console.log('unmounting...');
}, [])
这样只会在组件初次渲染的时候打印 mounted,在组件卸载后打印: unmounting。
不过,这隐藏了一个问题:传递空数组容易出现bug。如果咱们添加了依赖项,那么很容易忘记向其中添加项,如果错过了一个依赖项,那么该值将在下一次运行useEffect时失效,并且可能会导致一些奇怪的问题。
只在挂载的时候执行
// 存储对 input 的DOM节点的引用
const inputRef = useRef();
// 将输入值存储在状态中
const [value, setValue] = useState("");
useEffect(
() => {
// 这在第一次渲染之后运行
console.log("render");
// inputRef.current.focus();
},
// effect 依赖 inputRef
[inputRef]
);
使用 useEffect 获取数据并显示它
const [posts, setPosts] = useState([]);
useEffect(async () => {
const res = await fetch(
"https://www.reddit.com/r/reactjs.json"
);
const json = await res.json();
setPosts(json.data.children.map(c => c.data));
},[setPosts]); // 这里没有传入第二个参数,每次渲染都会执行此函数?
当数据改变时重新获取
const [posts, setPosts] = useState([]);
useEffect(async () => {
const res = await fetch(
`https://www.test.com/r/${test}.json`
);
const json = await res.json();
setPosts(json.data.children.map(c => c.data));
// 当`test`改变时重新运行useEffect:
}, [test, setPosts]);
可以对比Vue的watch 和 小程序的 behaivor
useLayoutEffect
已经形成真实DOM之后执行,但是是render之前
useEffect 在浏览器渲染完成后执行
useLayoutEffect 在浏览器渲染前执行
useLayoutEffect 总比 useEffect 先执行
useLayoutEffect 里的任务最好影响了 Layout
为了用户体验,优先使用 useEffect (优先渲染)
用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.
useState(initial)返回一个数组,其中第一项是状态值,第二项是一个更新状态的函数。
-
要确保对useState()的多次调用在渲染之间始终保持相同的顺序
-
函数组件有状态,在组件的函数体中调用useState()
-
在单个组件中可以有多个状态:调用多次useState()
-
当初始状态开销很大时,延迟初始化很方便。使用计算初始状态的回调调用useState(computeInitialState),并且此回调仅在初始渲染时执行一次
-
必须确保使用useState()遵循 Hook 规则
-
当闭包捕获过时的状态变量时,就会出现过时状态的问题。可以通过使用一个回调来更新状态来解决这个问题,这个回调会根据先前的状态来计算新的状态。
-
可以使用useState()来管理一个简单的状态。为了处理更复杂的状态,使用useReducer() hook。
const [state, setState] = useState(initialState);
// 将状态更改为 'newState' 并触发重新渲染
setState(newState);
// 重新渲染`state`后的值为`newState`
调用规则:
-
仅顶层调用 Hook :不能在循环,条件,嵌套函数等中调用useState()。在多个useState()调用中,渲染之间的调用顺序必须相同。
-
仅从React 函数调用 Hook:必须仅在函数组件或自定义钩子内部调用useState()。 使用 setState(newState)来更新状态值。另外,如果需要根据先前的状态更新状态,可以使用回调函数setState(prevState => newState)。
useRef存储对DOM节点的引用,它还相当于 this , 可以存放任何变量
可以用来引用 DOM 对象,也可以用来引用普通对象
useRef类似于React.createRef。
createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。
useRef可以很好的解决闭包带来的不方便性.可以在各种库中看到它的身影, 比如 react-use 中的 useInterval , usePrevious 。。。
值得注意的是,当 useRef 的内容发生变化时,它不会通知您。更改.current属性不会导致重新呈现。因为他一直是一个引用 .
function App() {
const refInput = React.useRef(null);
React.useEffect(() => {
refInput.current && refInput.current.focus();
}, []);
return <input ref={refInput} />;
}
应用场景:父组件调用子组件方法
function Child(props, ref) {
const [text, setText] = useState("");
// 该 hook 需要定义抛出给父组件的可以使用的 api 方法
// 相当于代理了子组件的方法
useImperativeHandle(ref, () => ({
setTextByParent(text = "") {
setText(text);
},
}));
return <p>text: {text}</p>;
}
// 函数组件需要使用 forwardRef 包裹
const ForwardChild = forwardRef(Child);
function Parent() {
const ref = useRef(null);
return (
<>
<ForwardChild ref={ref} />
<button
onClick={() => {
ref.current &&
ref.current.setTextByParent("text updated by parent component");
}}
>
set text by parent
</button>
</>
);
}
useRef保存任何可变得值,类似于在 class 中使用实例字段的方式。它创建的是一个普通js对象
useRef() 和自建一个 {current: ...} 对象的唯一区别是:useRef 会在每次渲染时返回同一个 ref 对象
forwardRef(props 无法传递 ref 属性)
实现 ref 的传递:由于 props 不包含 ref,所以需要 forwardRef
import React, {forwardRef, useRef} from 'react';
function App(){
const buttonRef = useRef(null)
return (
<div>
<Button ref={buttonRef}>按钮</Button2>
</div>
)
}
const Button = forwardRef((props, ref) => {
console.log(ref) //可以拿到ref对button的引用,forwardRef仅限于函数组件,class 组件是默认可以使用 ref 的
return <button ref={ref} {...props} />;
})
//const Button = (props) => {
// console.log(props) // {ref: undefined, children: "按钮"}
// return <button {...props} />
//}
useImperativeHandle
正常情况下 ref 是不能挂在到函数组件上的,因为函数组件没有实例,但是 useImperativeHandle 为我们提供了一个类似实例的东西。 它帮助我们通过 useImperativeHandle 的第 2 个参数,所返回的对象的内容挂载到 父组件的 ref.current 上。
forwardRef会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。
import React, { forwardRef, useImperativeHandle, useEffect, useRef } from 'react'
const TestRef = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
// 挂载到父组件ref.current上
open() {
console.log("open") //打印出open
}
}))
})
function App () {
const ref = useRef()
useEffect(() => {
ref.current.open() //获取到父函数
},[])
return(
<>
<div>石小阳</div>
<TestRef ref={ref}></TestRef>
</>
)
}
export default App
ref
普通的类组件有实例所以可以用过 React.createRef() 挂载到节点或组件上,然后通过 this 获取到该节点或组件。
class RefTest extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();
}
componentDidMount(){
console.log(this.myRef.current);
}
render(){
return <input ref={this.myRef}/>
}
}
useMemo表示对函数求值进行缓存,当依赖没有发生变化时,不执行计算,直接返回缓存结果
引入于16.6.0
有点像 vue 的计算属性,对于一些复杂耗时的计算,使用它能够优化程序的性能。
第一个参数是 () => value
第二个参数是依赖 [m, n]
只有当依赖变化时,才会计算出新的 value
如果依赖不变,那么就重用之前的
这不就是 Vue 2的 computed 吗?
如果你的 value 是一个函数,那么你就要写成useMemo(() => x => console.log(x)),这是一个返回函数的函数,是不是很难用?于是就有了useCallback
使用方式与useCallback类似,只不过useMemo返回的是一个值。
const newValue = useMemo(() => Math.sqrt(value), [value]);
useMemo() 是在 render 期间执行的,所以不能进行一些额外的副操作,比如网络请求等。
useCallback是返回一个memoized函数,用来对函数进行缓存,防止总是重复的生成新的函数。
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
useCallback(x => console.log(x), [m]) 等价于
useMemo( () => x => console.log(x), [m])
可以通过把函数add进Set中,来获取set.size,来监控函数是否被缓存
export function Demo() {
const [count, setCount] = useState(0);
/*
//count值始终为1
const changeCount = () => {
setCount(count + 1);
};
这是因为useCallback的第二个参数没有指定,它只会在第一次时进行changeValue闭包缓存,所以初始是0,缓存后就一直为0,执行一次自增,所以值始终是1。
*/
//指定useCallback的依赖,当依赖发生变化时,其自动更新缓存
const changeValue = useCallback(
() => {
setValue(value + 1);
},
[value]
);
return (
<div>
{/*ComplexComponent是一个十分复杂的组件*/}
<ComplexComponent onIncrement={changeCount} />
<button onClick={changeValue}>count</button>
</div>
);
}
使用场景:通常在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理
useRequest
const { data: monitorPageConfList, loading } = useRequest(
async () => {
return monitorApi.getMonitorPageConf();
},
{
refreshDeps: [],
onError: (error) => {
message.error(`获取监控配置失败:${error.message}`);
}
},
);
const { data: aAndbList } = useRequest(
async () => {
const aList = await getAList(branchType, Number(branchId));
let imageQuality = null;
if (instance) {
bList = await getBlist(instance.id);
}
return { aList, bList };
},
{
refreshDeps: [branchId],
},
);