如何学习React-Hook
本篇内容主要讲解“如何学习React-Hook”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何学习React-Hook”吧!
数据绑定
在react中state的概念是内部状态的管理,它的变更直接会影响页面的渲染。
在hook中把setState拆开了。每一个单个的state的都会是一个单个的状态,单个状态的变更不会影响其他state。我们可以通过useState实现单个状态的初始化定义。
useState的参数可以是一个数字、字符串、布尔值、数组或对象,也可以是一个函数。
同样我们避免不了会使用集合(对象)来处理一些逻辑。
const [count, setCount] = useState(0); const [count, setCount] = useState(() => 0); const [obj, setObj] = useState({}); setObj((prevObj) => { // 也可以使用 Object.assign return { ...prevObj, age: 23 }; });
一般我们会定义一个初始值initialState,如果这个初始值,需要额外的计算开销,我们可以定义一个函数来处理。需要注意的是,useState 函数只会在初始渲染的时候被调用。
const [state, setState] = useState(() => { // 额外的操作someExpensiveComputation const initialState = someExpensiveComputation(props); return initialState; });
对于多个state集合的处理,还有另一种方案就是useReducer。
比如单个 state 的状态会影响多个 state 的值的时候。
比如多个 state 的状态会随着某种类型的改变而改变。
如下多个 state 会随着登录、登出、刷新 token 这三种状态的改变而改变。
const [state, dispatch] = React.useReducer( (prevState, action) => { switch (action.type) { case "RESTORE_TOKEN": return { ...prevState, userToken: action.token, isLoading: false, }; case "SIGN_IN": return { ...prevState, isSignout: false, userToken: action.token, }; case "SIGN_OUT": return { ...prevState, isSignout: true, userToken: null, }; } }, { isLoading: true, isSignout: false, userToken: null, } );
副作用
hook 提供了一种新的概念来代替生命周期函数,就是useEffect副作用。它被看作是从 React 的纯函数式世界通往命令式世界的逃生通道。
它的执行时机是在屏幕元素渲染结束后延迟执行。
它的作用有:
它可以监控 state 值的变化
它可以处理只运行一次的逻辑,有点类似生命周期 componentDidMount 和 componentWillUnmount 的思维模式,
添加订阅、设置定时器、发送网络请求
更多其他
// 通过的第二个参数来实现只执行一次或监控state值 useEffect(() => { // ... }, []); // useEffect第一个参数的返回函数就是componentWillUnmount的思想,在组件卸载之前进行 useEffect(() => { const subscription = props.source.subscribe(); return () => { // 清除订阅 subscription.unsubscribe(); }; });
但是这种延迟执行的机制不能满足我们所有的场景,如果我们想要实现屏幕绘制和副作用同步执行,比如实时修改dom结构等这样的场景,useEffect无法满足,会出现闪屏的效果。
我们可以通过useLayoutEffect来实现,它的执行时机是在组件加载完成后,屏幕绘制之前进行。但这样也有缺点就是阻塞屏幕渲染,可能会出现白屏或停顿。
所以useLayoutEffect的使用场景:
防止闪烁,比较耗时的计算
Dom操作
componentDidMount和componentDidUpdate的场景
如果只是单独的获取(get操作)就没有必要使用useLayoutEffect。
组件传值
组件传值的核心:
父传子,通过在子组件设置属性;
子传父,通过回调。
多级组件,通过中间状态管理
// 父组件 function Home() { const [currentTab, setCurrentTab] = useState("msg"); return ( <> <View style={styles.logo}> // 父传子 <TabView currentTab={currentTab} setCurrentTab={setCurrentTab} /> // 子传父 <CodeView code={code} changeCode={(code)=>setCurrentTab(code)} /> </View> <Text>{currentTab}</Text> </> ); } //子组件 function TabView({ currentTab, setCurrentTab }) { return ( <View style={styles.logo}> <Text>{currentTab}</Text> <Button title="修改tab" onPress={() => { setCurrentTab("pass"); }} /> </View> ); } //子传父 function CodeView({ code, changeCode }) { return ( <View style={styles.logo}> <Text>{code}</Text> <Button title="修改tab" onPress={changeCode} /> </View> ); }
多组件的传值,可以通过context来处理。
export const MyContent = React.createContext({}); function Home() { return ( <MyContent.Provider value={{ currentTab, phoneValue, codeValue, setPhoneValue, setCodeValue, }} > <FormItem /> <SwitchItemView /> </MyContent.Provider> ); } function FormItem() { const { phoneValue, setPhoneValue } = useContext(MyContent); return ( <View style={styles.logo}> <Text>{phoneValue}</Text> {} </View> ); } function SwitchItemView() { const { codeValue, setCodeValue } = useContext(MyContent); return ( <View style={styles.logo}> <Text>{phoneValue}</Text> {} </View> ); }
元素节点操作
hook通过useRef来创建节点对象,然后通过ref挂载,通过current来获取。
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
我们可能会封装一些逻辑,自定义一些属性,暴露给父元素,那么我们就会用到useImperativeHandle和forwardRef。
function FancyInput(props, pref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput); <FancyInput ref={inputRef} /> // 父组件可以直接调用inputRef.current.focus()
因为 ref 对象不会把当前 ref 值的变化通知给我们,所以我们必须通过useState和useCallback实现。
function MeasureExample() { const [height, setHeight] = useState(0); const measuredRef = useCallback((node) => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, []); return ( <> <h2 ref={measuredRef}>Hello, world</h2> <h3>The above header is {Math.round(height)}px tall</h3> </> ); }
自定义hook
在代码中,我们会有一些共用的逻辑,我们可以抽离出来比如自定义的防抖节流,自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
const useDebounce = (fn, ms = 30, deps = []) => { let timeout = useRef(); useEffect(() => { if (timeout.current) clearTimeout(timeout.current); timeout.current = setTimeout(() => { fn(); }, ms); }, deps); const cancel = () => { clearTimeout(timeout.current); timeout = null; }; return [cancel]; }; export default useDebounce; const Home = (props) => { const [a, setA] = useState(0); const [b, setB] = useState(0); const [cancel] = useDebounce( () => { setB(a); }, 2000, [a] ); const changeIpt = (e) => { setA(e.target.value); }; return ( <div> <input type="text" onChange={changeIpt} /> {b} {a} </div> ); };
性能优化
单向数据流,各组件层次分明,状态明确,但是当项目体量大,组件嵌套多的时候,性能损耗也非常大,所以我们会做一些性能优化的工作。
可能的优化场景有:
单个 state 的更新会影响全局,有一点需要注意的是,被context包裹的组件,只要value的任何一个变动,都会重新渲染,useMemo和useCallback就会失效。
相同的输入,不再重新计算
父组件的函数在子组件使用的时候
其实useMemo和useCallback的核心思想相同,都是记录上一次的输入,如果下一次输入与上一次相同,将不会计算,直接获取上一次的结果。他们的区别只是形式上的,useMemo返回一个 memoized 值。而useCallback返回的是memoized回调函数。
useMemo缓存计算结果的值。
useCallback主要用于缓存函数。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); // useMemo const [count, setCount] = useState(1); const [val, setValue] = useState(""); const expensive = useMemo(() => { let sum = 0; for (let i = 0; i < count * 100; i++) { sum += i; } return sum; }, [count]); return ( <div> <h5> {count}-{expensive} </h5> {val} <div> <button onClick={() => setCount(count + 1)}>+c1</button> <input value={val} onChange={(event) => setValue(event.target.value)} /> </div> </div> );
捎带了解一下memoized,简单讲就是把函数的计算结果缓存起来,比如递归。
const memoize = function(fn) { const cache = {}; return function() { const key = JSON.stringify(arguments); var value = cache[key]; if(!value) { console.log('新值,执行中...'); // 为了了解过程加入的log,正式场合应该去掉 value = [fn.apply(this, arguments)]; // 放在一个数组中,方便应对undefined,null等异常情况 cache[key] = value; } else { console.log('来自缓存'); // 为了了解过程加入的log,正式场合应该去掉 } return value[0]; } } module.exports = memoize; const memoize = require('./memoize.js'); const log = console.log; // 斐波那契数组 const fibonacci = (n) => { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); }; const memoizeFibonacci = memoize(fibonacci); log(memoizeFibonacci(45)); // 新值,执行中...; 1134903170 // 等待时间比较长 log(memoizeFibonacci(45)); // 来自缓存; 1134903170 log(memoizeFibonacci(45)); // 来自缓存; 1134903170 log(memoizeFibonacci(45)); // 来自缓存; 1134903170
到此,相信大家对“如何学习React-Hook”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341