hooks_hooks react - CSDN
精华内容
参与话题
  • React Hooks

    千次阅读 2018-11-15 16:35:22
    Hooks Hooks是React v16.7.0-alpha的新特新,可以与react state相结合, 使函数组件功能类似于class一样。但是在Class类内部,不可以使用hooks. React 提供了几个像useState,useEffect, useContext 等的内置钩子函数...

    Hooks

    Hooks是React v16.7.0-alpha的新特新,可以与react state相结合, 使函数组件功能类似于class一样。但是在Class类内部,不可以使用hooks.
    React 提供了几个像useState,useEffect, useContext 等的内置钩子函数,我们也可以自定义钩子函数在组件之间来复用state。

    useState(内置hook)

    import {useState} from 'reaact';
    function Example(){
    const [count, setCount]=useState(0);
    return(
    	<div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
    )}
    

    在函数组件内部,不存在指向函数自身的this, 因此不可以声明或者调用this.state.
    useState hooks提供给函数等同于class内部this.state的作用。state变量通过react进行维护。
    useState 是一个HOOK, 在函数内部调用,可以给函数本身添加state,react会保存并渲染页面。useState返回一组值:当前State, 和更新state的方法,这个方法可以在函数内部或其他地方调用。与react this.setState方法类似,但是不同的是,它并非将旧值与新值进行合并得到新值.

    useState只接受一个参数,即state的初始值,eg. useState(0), 0即为state的初始值。并且只在初次渲染时调用。与this.state不同的是,hook 的state不必一定为一个对象,但如果想定义为对象也是可以的。

    定义多个State变量

    function ManyStates(){
    	const [age,setAge]=useState(42);
    	const [fruit,setFruit]=useState('orange');
    	const [todos,setTodos]=useState([{text:'learn hooks'}])
    

    数组的解构赋值使我们可以定义多个变量。如果我们多次调用useState,react会同时进行编译。

    Effect Hook(内置hook)

    在react 组件内部,我们通常会进行一些数据获取,提交,或手动改变DOM状态的操作, 这种对其他环境有影响或依赖的又称为:side effect(副作用), Effect Hook: useEffect ,提供函数组件同样的功效,作用等同于class组件内部的componentDidMount, componentDidUpdate, componentWillUnmount,只是将这些统一成单一API。

    import {useState, useEffect} from 'react';
    function Eample(){
    	const [count,setCount]=useState(0);
    	// Similar to componentDidMount and componentDidUpdate:
      useEffect(() => {
        // Update the document title using the browser API
        document.title = `You clicked ${count} times`;
      });
      return (
      	<div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
    )
    }
    

    当调用useEffect,即告诉React在DOM变化后执行’effect’方法。Effects在组件内部声明,因此可以读取组件内的props, state。默认情况下,react会在每次render后都去执行effects里的方法,也包括第一次render.
    Effects也可以通过返回一个函数选择性的指定如何’clean up’当函数unMount时。
    eg.

    import { useState, useEffect } from 'react';
    
    function FriendStatus(props) {
      const [isOnline, setIsOnline] = useState(null);
    
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
    
      if (isOnline === null) {
        return 'Loading...';
      }
      return isOnline ? 'Online' : 'Offline';
    }
    

    上例中,React在组件卸载时会调用ChatAPI的unsubscribe方法,同样在随后的render中也会调用。(如果props.friend.id没有变化,你也可以告诉React忽略re-subscribing,如果你想。)

    与useState类似,你也可以使用多个useEffect在函数组件中:

    function FriendStatusWithCounter(props) {
      const [count, setCount] = useState(0);
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    
      const [isOnline, setIsOnline] = useState(null);
      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
    
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
      // ...
    

    上例中,多个useEffect里面传入的函数是彼此独立的,由此可以保证effect里面读取的state值将会是最新的,在网页每次重新编译后,我们执行一个新的effect,替代前面的state值。useEffect并不会阻塞浏览器更新页面,它们为异步执行。在某些特殊情况下需要同步执行,例如计算页面布局,这时可以使用useLayoutEffect Hook 替代useEffect. useLayoutEffect在DOM计算完成后同步执行,与componentDidMount, componentDidUpdate执行时间类似,在不确定用哪种effect时,使用useLayoutEffect风险较小。

    Hooks可以将组件中相关的生命周期函数写在一起,而不是将它们分开在不同的生命周期函数中。

    Effects without cleanup

    在class组件中我们执行side effects在componentDidMount/componentDidUpdate中,如果我们想执行同一方法在组件每次render之后,必须将相同方法在didMount, didUpdate两个阶段都调用一次。
    在Hooks中我们只需要在useEffects中将此方法写一次即可每次render后都进行调用。
    默认情况下,useEffect在首次编译和每次更新数据都会执行,即每次编译后都执行。我们也可以进行优化:忽略某些编译。
    在Class组件中,componentDidUpdate可以通过prevProps,prevState来阻止update里函数的执行eg.

    componentDidUpdate(prevProps, prevState) {
      if (prevState.count !== this.state.count) {
        document.title = `You clicked ${this.state.count} times`;
      }
    }
    
    

    在useEffect Hook API中内置了这种比较,你可以向useEffect(()=>{}, [compareState])传入第二个参数:state数组。eg.

    useEffect(() => {
      document.title = `You clicked ${count} times`;
    }, [count]); // Only re-run the effect if count changes
    

    只有count值发生改变-上次render和此次render count值不同,react才会忽略此次render,不执行effect里的方法。
    这个也同样作用于 含有clean up 的effects

    useEffect(() => {
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
      return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
      };
    }, [props.friend.id]); // Only re-subscribe if props.friend.id changes
    
    

    如果使用这个优化,请确保数组内包含的值是从外部作用域获取,并且会随着时间变化,effect里面也会使用的。否则你effect里的代码将会从之前的编译中取值。
    如果你想执行一个effect和clean up 仅一次,你可以将useEffect第二个参数传入一个空数组[], 类似componentDidMount, componentWillUnmount.

    Effects with Cleanup
    useEffects第一个参数会返回一个函数作为cleanup 执行的方法。会在unmount,和每次re-render时执行。

    useContext(内置hook)

    const context=useContext(Context);
    
    

    class组件中context使用,会将组件包含在context.provider && context.consumer之间,consumer之间的组件会以render props的模式(传递一个函数作为children)来获得provider传递的参数,如下

    import React from "react";
    import ReactDOM from "react-dom";
    
    // Create a Context
    const NumberContext = React.createContext();
    // It returns an object with 2 values:
    // { Provider, Consumer }
    
    function App() {
      // Use the Provider to make a value available to all
      // children and grandchildren
      return (
        <NumberContext.Provider value={42}>
          <div>
            <Display />
          </div>
        </NumberContext.Provider>
      );
    }
    
    function Display() {
      // Use the Consumer to grab the value from context
      // Notice this component didn't get any props!
      return (
        <NumberContext.Consumer>
          {value => <div>The answer is {value}.</div>}
        </NumberContext.Consumer>
      );
    }
    
    ReactDOM.render(<App />, document.querySelector("#root"));
    

    在useContext hook 中,我们可以使用useContext(Context)-传入的参数为通过React.createContext获得的context对象,直接获取provider传递的值,改写consumer组件如下:

    // import useContext (or we could write React.useContext)
    import React, { useContext } from 'react';
    
    // ...
    
    function Display() {
      const value = useContext(NumberContext);
      return <div>The answer is {value}.</div>;
    } 
    

    如果consumer组件中有多层嵌套获得多个provider值,那么使用hook将会更简单:

    function HeaderBar() {
      return (
        <CurrentUser.Consumer>
          {user =>
            <Notifications.Consumer>
              {notifications =>
                <header>
                  Welcome back, {user.name}!
                  You have {notifications.length} notifications.
                </header>
              }
          }
        </CurrentUser.Consumer>
      );
    }
    
    //useContext改写如下:
    function HeaderBar() {
      const user = useContext(CurrentUser);
      const notifications = useContext(Notifications);
    
      return (
        <header>
          Welcome back, {user.name}!
          You have {notifications.length} notifications.
        </header>
      );
    }
    
    

    useReducer Hook(内置)

    Hooks规则

    1. 不要在循环,条件,或嵌套函数中使用,应该在顶层的React function中使用。由此确保在组件编译过程中,Hooks以相同的顺序进行调用,这保证了在众多useState, useEffect中React正确保存state状态。
    2. Hooks只能在React 函数组件调用,常规函数不能调用。
    3. eslint-plugin-react-hooks插件可以强化前两条规则: npm install eslint-plugin-react-hooks@next
    // ------------
    // First render
    // ------------
    useState('Mary')           // 1. Initialize the name state variable with 'Mary'
    useEffect(persistForm)     // 2. Add an effect for persisting the form
    useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
    useEffect(updateTitle)     // 4. Add an effect for updating the title
    
    // -------------
    // Second render
    // -------------
    useState('Mary')           // 1. Read the name state variable (argument is ignored)
    useEffect(persistForm)     // 2. Replace the effect for persisting the form
    useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
    useEffect(updateTitle)     // 4. Replace the effect for updating the title
    
    // ...
    

    自定义Hook

    在传统React class组件中,如果组件之间公用大部分逻辑和页面,通常会通过props传值, 或使用高阶组件的方式来返回不同组件。
    在Hook中,我们不需要写额外的组件,可以直接将共有部分抽取成自定义的hook函数,如下:

    import { useState, useEffect } from 'react';
    
    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
        };
      });
    
      return isOnline;
    }`
    

    useFriendStatus主要用于通过传入id返回online状态,所有组件共用同一个Hook,state也是相互独立的。

    展开全文
  • 前言 如释重负,好用的技术就应该越来越简单 React Hooks 是 React 16.8 从提案转为...通过自定义 Hooks 我们可以把应用中“状态相关”逻辑解耦出来,独立编写到我们自己的hooks 中。从而更加易于复用和独立测试。
  • 浅谈 Hooks

    千次阅读 2020-10-11 18:38:09
      如果你很熟悉 vue 与 react ,兴许你也觉得 vue3.0 抄袭了react,这项react 在不久前发布的新技术,在 vue3.0 中被重新搬上了舞台。也使它重新活跃在了人们的视野中,我技术不深,与大家分享我的见解和猜测。...

      如果你很熟悉 vue 与 react ,兴许你也觉得 vue3.0 抄袭了react,这项react 在不久前发布的新技术,在 vue3.0 中被重新搬上了舞台。也使它重新活跃在了人们的视野中,我技术不深,与大家分享我的见解和猜测。
    在这里插入图片描述


    useState
    使用状态

    const [n, setN] = React.useState(0)
    const [user, setUser] = React.useState({name: 'Jack', age: 18})
    

    注意事项:
    无局部更新能力

    如果state是一个对象,能否部分setState?
    答案是不行,因为setState不会帮我们合并属性
    那么useReducer会合并属性吗?也不会!
    因为React认为这应该是你自己要做的事情

    function App(){
        const [user, setUser] = React.useState({name: 'Jack', age: 18})
        const onClick = () =>{
            //setUser不可以局部更新,如果只改变其中一个,那么整个数据都会被覆盖
            // setUser({
            //  name: 'Frank'
            // })
            setUser({
                ...user, //拷贝之前的所有属性
                name: 'Frank' //这里的name覆盖之前的name
            })
        }
        return (
            <div className='App'>
                <h1>{user.name}</h1>
                <h2>{user.age}</h2>
                <button onClick={onClick}>Click</button>
            </div>
        )
    }
    

    注意事项:
    地址要进行改变

    setState(obj) 如果obj地址不变,那么React就认为数据没有变化,不会更新视图

    useState接受函数

    const [state, setState] = useState(() => {return initialState})
    
    该函数返回初始state,且只执行一次
    

    在这里插入图片描述



    setState接受函数

    setN(i => i + 1)
    
    如果你能接受这种形式,应该优先使用这种形式
    

    在这里插入图片描述


    useReducer

    用来践行Flux/Redux思想

    一、创建初始值initialState
    
    二、创建所有操作reducer(state, action);
    
    三、传给userReducer,得到读和写API
    
    四、调用写({type: '操作类型'})
    
    总的来说,useReducer 是 useState 的复杂版
    

    在这里插入图片描述


    如何代替 Redux

    一、将数据集中在一个 store 对象
    
    二、将所有操作集中在 reducer
    
    三、创建一个 Context
    
    四、创建对数据的读取 API
    
    五、将第四步的内容放到第三步的 Context
    
    六、用 Context.Provider 将 Context 提供给所有组件
    
    七、各个组件用 useContext 获取读写API
    
    import React, { useReducer, useContext, useEffect } from "react";
    import ReactDOM from "react-dom";
    
    const store = {
        user: null,
        books: null,
        movies: null
    };
    
    function reducer(state, action) {
        switch (action.type) {
            case "setUser":
                return { ...state, user: action.user };
            case "setBooks":
                return { ...state, books: action.books };
            case "setMovies":
                return { ...state, movies: action.movies };
            default:
                throw new Error();
        }
    }
    
    const Context = React.createContext(null);
    
    function App() {
        const [state, dispatch] = useReducer(reducer, store);
    
        const api = { state, dispatch };
        return (
            <Context.Provider value={api}>
                <User />
                <hr />
                <Books />
                <Movies />
            </Context.Provider>
        );
    }
    
    function User() {
        const { state, dispatch } = useContext(Context);
        useEffect(() => {
            ajax("/user").then(user => {
                dispatch({ type: "setUser", user: user });
            });
        }, []);
        return (
            <div>
                <h1>个人信息</h1>
                <div>name: {state.user ? state.user.name : ""}</div>
            </div>
        );
    }
    
    function Books() {
        const { state, dispatch } = useContext(Context);
        useEffect(() => {
            ajax("/books").then(books => {
                dispatch({ type: "setBooks", books: books });
            });
        }, []);
        return (
            <div>
                <h1>我的书籍</h1>
                <ol>
                    {state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加载中"}
                </ol>
            </div>
        );
    }
    
    function Movies() {
        const { state, dispatch } = useContext(Context);
        useEffect(() => {
            ajax("/movies").then(movies => {
                dispatch({ type: "setMovies", movies: movies });
            });
        }, []);
        return (
            <div>
                <h1>我的电影</h1>
                <ol>
                    {state.movies
                        ? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>)
                        : "加载中"}
                </ol>
            </div>
        );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    
    // 帮助函数
    
    // 假 ajax
    // 两秒钟后,根据 path 返回一个对象,必定成功不会失败
    function ajax(path) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (path === "/user") {
                    resolve({
                        id: 1,
                        name: "Frank"
                    });
                } else if (path === "/books") {
                    resolve([
                        {
                            id: 1,
                            name: "JavaScript 高级程序设计"
                        },
                        {
                            id: 2,
                            name: "JavaScript 精粹"
                        }
                    ]);
                } else if (path === "/movies") {
                    resolve([
                        {
                            id: 1,
                            name: "爱在黎明破晓前"
                        },
                        {
                            id: 2,
                            name: "恋恋笔记本"
                        }
                    ]);
                }
            }, 2000);
        });
    }
    

    useContext

    全局变量是全局的上下文
    上下文是局部的全局变量

    
    
        一、使用 C = createContext(initial) 创建上下文
    
        二、使用 <C.Provider> 圈定作用域
    
        三、在作用域内使用 useContext(C)来使用上下文
    
    

    在这里插入图片描述


    useEffect
    副作用 (API 名字叫得不好)

    
        对环境的改变即为副作用,如修改 document.title
    
        但我们不一定非要把副作用放在 useEffect 里面
    
        实际上叫做 afterRender 更好,每次render后执行
    
    

    用途

    一、作为 componentDidMount 使用,[ ] 作第二个参数
    
    二、作为 componentDidUpdate 使用,可指定依赖
    
    三、作为 componentWillUnmount 使用,通过 return
    
    四、以上三种用途可同时存在
    

    在这里插入图片描述


    特点

    如果同时存在多个 useEffect, 会按照出现次序执行

    useLayoutEffect
    布局副作用

        useEffect 在浏览器渲染完成后执行
        useLayoutEffect 在浏览器渲染前执行
    
    function App1() {
        const [n, setN] = useState(0)
        const time = useRef(null)
        const onClick = () => {
            setN(i => i + 1)
            time.current = performance.now()
        }
        useLayoutEffect(() => {
            if (time.current) {
                console.log(performance.now() - time.current) //大概是0.7ms
            }
        })
        useEffect(() => {
            if (time.current) {
                console.log(performance.now() - time.current) //大概是2.7ms
            }
        })
        return (
            <div className="App">
                <h1>n: {n}</h1>
                <button onClick={onClick}>Click</button>
            </div>
        );
    }
    

    在这里插入图片描述



    特点

        useLayoutEffect 总比 useEffect 先执行
        useLayoutEffect 里的任务最好影响了 Layout
    
    /* useLayoutEffect比useEffect先执行 */
    function App2() {
        const [n, setN] = useState(0)
        const onClick = () => {
            setN(i => i + 1)
        }
        //执行顺序打印出 2、3、1
        useEffect(() => {
            console.log(1)
        })
        useLayoutEffect(() => {
            console.log(2)
        })
        useLayoutEffect(() => {
            console.log(3)
        })
        return (
            <div className="App">
                <h1>n: {n}</h1>
                <button onClick={onClick}>Click</button>
            </div>
        );
    }
    

    经验

    为了用户体验,优先使用 useEffect (优先渲染)

    useMemo

    要理解 React.useMemo

        需要先讲 React.memo
        React默认有多余的render
    
    function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
            setN(n + 1);
        };
    
        return (
            <div className="App">
                <div>
                    {/*点击button会重新执行Child组件*/}
                    <button onClick={onClick}>update n {n}</button>
                </div>
                <Child data={m}/>
                {/* <Child2 data={m}/> */}
            </div>
        );
    }
    function Child(props) {
        console.log("child 执行了");
        console.log('假设这里有大量代码')
        return <div>child: {props.data}</div>;
    }
    const Child2 = React.memo(Child);
    
        将代码中的 Child 用React.memo(Child) 代替
        如果 props 不变,就没有必要再次执行一个函数组件
        最终代码:
    
    function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
            setN(n + 1);
        };
    
        return (
            <div className="App">
                <div>
                    {/*点击button会重新执行Child组件*/}
                    <button onClick={onClick}>update n {n}</button>
                </div>
                <Child data={m}/>
            </div>
        );
    }
    const Child = React.memo(props => {
            console.log("child 执行了");
            console.log('假设这里有大量代码')
            return <div>child: {props.data}</div>;
    });
    

    但是

        这玩意有一个bug
        添加了监听函数之后,一秒破功因为 App 运行时,会再次执行 onClickChild,生成新的函数
        新旧函数虽然功能一样,但是地址引用不一样!
    
    function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
            setN(n + 1);
        };
        const onClickChild = () => {}
        return (
            <div className="App">
                <div>
                    {/*点击button会重新执行Child组件*/}
                    <button onClick={onClick}>update n {n}</button>
                </div>
                {/*但是如果传了一个引用,则React.memo无效。因为引用是不相等的*/}
                <Child data={m} onClick={onClickChild}/>
            </div>
        );
    }
    //使用React.memo可以解决重新执行Child组件的问题
    const Child = React.memo(props => {
            console.log("child 执行了");
            console.log('假设这里有大量代码')
            return <div onClick={props.onClick}>child: {props.data}</div>;
    });
    

    怎么办? 用useMemo:

    function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
            setN(n + 1);
        };
        const onClick1 = () => {
            setM(m + 1);
        };
        const onClickChild = () => {}
        const onClickChild1 = useMemo(() => {
            return () => {
                console.log(`on click child m: ${m}`)
            }
        }, [m])
        return (
            <div className="App">
                <div>
                    {/*点击button会重新执行Child组件*/}
                    <button onClick={onClick}>update n {n}</button>
                    <button onClick={onClick1}>update m {m}</button>
                </div>
                {/*但是如果传了一个引用,则React.memo无效。因为引用是不相等的*/}
                {/*<Child data={m} onClick={onClickChild}/>*/}
                {/*onClickChild1使用useMemo可以消除此bug*/}
                <Child data={m} onClick={onClickChild1}/>
            </div>
        );
    }
    //使用React.memo可以解决重新执行Child组件的问题
    const Child = React.memo(props => {
            console.log("child 执行了");
            console.log('假设这里有大量代码')
            return <div onClick={props.onClick}>child: {props.data}</div>;
    });
    

    useMemo
    特点

    第一个参数是 () => value
    
    第二个参数是依赖 [m, n]
    
    只有当依赖变化时,才会计算出新的 value
    
    如果依赖不变,那么就重用之前的 value
    
    这不就是 Vue 2的 computed 吗?
    

    注意

        如果你的 value 是一个函数,那么你就要写成useMemo(() => x => console.log(x))
        这是一个返回函数的函数
        是不是很难用?于是就有了useCallback
    

    useCallback
    用法

    useCallback(x => console.log(x), [m]) 等价于
    useMemo( () => x => console.log(x), [m])
    

    forwardRef
    useRef

    可以用来引用 DOM 对象
    也可以用来引用普通对象
    

    forwardRef

    props 无法传递 ref 属性

    function App(){
        const buttonRef = useRef(null)
        return (
            <div>
                <Button ref={buttonRef}>按钮</Button>
                {/* 控制台报错:
                        Warning: Function components cannot be given refs.
                      Attempts to access this ref will fail.
                      Did you mean to use React.forwardRef()?
                  */}
            </div>
        )
    }
    
    const Button = (props) => {
        console.log(props) // {ref: undefined, children: "按钮"}
        return <button {...props} />
    }
    

    实现 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} />;
    })
    

    自定义 Hook
    封装数据操作

    // useList.js
    import {useState, useEffect} from 'react'
    
    const useList = () => {
        const [list, setList] = useState(null)
        useEffect(() => {
            ajax().then(list => {
                setList(list)
            })
        }, []) //确保只在第一次运行
        return {
            list,
            setList
        }
    }
    export default useList
    
    function ajax(){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve([
                    {id: 1, name: 'Frank'},
                    {id: 2, name: 'Jack'},
                    {id: 3, name: 'Alice'},
                    {id: 4, name: 'Bob'},
                    {id: 5, name: 'Han'}
                ])
            }, 1000)
        })
    }
    
    //index.js
    import useList from './hooks/useList'
    
    function App(){
        const {list, setList} = useList()
        return (
            <div>
                <h1>List</h1>
                {
                    list ? (
                        <ol>
                            {
                                list.map(item => {
                                    return <li key={item.id}>{item.name}</li>
                                })
                            }
                        </ol>
                    ):(
                        '加载中...'
                    )
                }
            </div>
        )
    }
    

    在这里插入图片描述



    再比如下面这个

    // useList.js
    import {useState, useEffect} from 'react'
    
    const useList = () => {
        const [list, setList] = useState(null)
        useEffect(() => {
            ajax().then(list => {
                setList(list)
            })
        }, []) //确保只在第一次运行
        return {
            list,
            addItem: name => {
                setList([...list, {id: Math.random(), name}])
            },
            deleteIndex: index => {
                setList(list.slice(0, index).concat(list.slice(index + 1)))
            }
        }
    }
    export default useList
    
    function ajax(){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve([
                    {id: 1, name: 'Frank'},
                    {id: 2, name: 'Jack'},
                    {id: 3, name: 'Alice'},
                    {id: 4, name: 'Bob'},
                    {id: 5, name: 'Han'}
                ])
            }, 1000)
        })
    }
    
    //index.js
    import useList from './hooks/useList'
    
    function App() {
        const {list, deleteIndex} = useList()
        return (
            <div>
                <h1>List</h1>
                {
                    list ? (
                        <ol>
                            {
                                list.map((item,index) => {
                                    return (
                                        <li key={item.id}>
                                            {item.name}
                                            <button
                                                onClick={() => {
                                                    deleteIndex(index);
                                                }}
                                            >
                                                x
                                            </button>
                                        </li>
                                    )
                                })
                            }
                        </ol>
                    ) : (
                        '加载中...'
                    )
                }
            </div>
        )
    }
    

    在这里插入图片描述


    你还可以在自定义 Hook 里使用 Context
    useState 只说了不能在 if 里,没说不能在函数里运行,只要这个函数在函数组件里运行即可
    自定义 Hook 完全可以代替 Redux
    

    在这里插入图片描述  前端的学习不是一蹴而就,不积跬步无以至千里,不积小流无以成江海。持续不断的努力才能让你我有所收获,专业的知识还得到机构去学习,培训机构的设立有其存在的必然性,你钱花对了吗?


    推荐阅读:
    5分钟学会 vue 路由守卫
    10分钟搞懂 vuex
    vue 组件设计原则
    Tree Shaking 概念详解
    深入解读 vue 修饰符 sync
    月入过万的都看它

    展开全文
  • hooks

    千次阅读 2019-12-26 15:05:33
    前言:hooks出了已有大半年了,关注的公众号也大都推了关于hooks的文章,可是因为工作中一直用的是class,所以一直没有用,也没有学,趁着这段时间项目不那么干,将hooks系统性的学习一下,并做笔记记录一下。...

    Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

    前言:hooks出了已有大半年了,关注的公众号也大都推了关于hooks的文章,可是因为工作中一直用的是class,所以一直没有用,也没有学,趁着这段时间项目不那么干,将hooks系统性的学习一下,并做笔记记录一下。

    目录
    1. useState
    2. userEffect
    3. userEffect实现componentWillUnmont
    4. 父子组件传值
    5. userContext
    6. userReducer
    7. useReducer替代Redux案例
    8. useMemo
    9. useRef
    10. useCallBack
    11. 自定义函数

    一:useState

    在组件中,我们难免使用state来进行数据的实时响应,这是react框架的一大特性,只需更改state,组件就会重新渲染,试图也会响应更新。

    不同于reactclass可以直接定义state,或者是在constructor中使用this.state来直接定义state值,在hooks中使用state需要useState函数,如下:

    import React, { useState, useEffect } from 'react';
    
    function Hooks() {
      const [count, setCount] = useState(0);
      const [age] = useState(16);
      useEffect(() => {
        console.log(count);
      });
      return (
        <div>
          <p>小女子芳年{age}</p>
          <p>计数器目前值为{count}</p>
          <button type="button" onClick={() => { setCount(count + 1); }}>点击+1</button>
          <button type="button" onClick={() => { setCount(count - 1); }}>点击-1</button>
        </div>
      );
    }
    
    export default Hooks;
    

    在上面的例子中,我们使用了useState定义了两个state变量,countage,其中定义count的时候还定义了setCount,就是用来改变count值的函数。在class类中,改变state是使用setState函数,而在hooks中是定义变量的同时定义一个改变变量的函数。

    userState是一个方法,方法返回值为当前state以及更新state的函数,所以,在上面的例子中,我们用const [count, setCount] = useState(0);将count和setCount解构出来,而userState方法的参数就是state的初始值。当然count和与之对应的改变函数名称并不一定非得是setCount,名称可以随便起,只要是一块解构出来的即可。

    class组件中,我们可以用setState一次更改多个state值而只渲染一次,同样的,在hooks中,我们调用多个改变state的方法,也只是渲染一次。

    二:userEffect 回目录

    class组件中,有生命周期的概念,最常用的,我们通常会在componentDidMount这个生命周期中做数据请求,偶尔,我们也会用一些其它的生命周期,像是componentDidUpdatacomponentWillReceiveProps等。在hooks中,没有生命周期的概念,但是,有副作用函数useEffect。

    使用useEffect,和使用useState相同,必须得先引入import React, { useState, useEffect } from 'react';,默认情况下,useEffect会在第一次和每次更新之后都会执行,useEffect函数接受两个参数,第一个参数是一个函数,每次执行的就是函数中的内容,第二个函数是个数组,数组中可选择性写state中的数据,代表只有当数组中的state发生变化是才执行函数内的语句。如果是个空数组,代表只执行一次,类似于componentDidUpdata。所以,向后端请求可以写成下面这种方式:

    // 页面进来只调用一次
    useEffect(()=>{
        axios.get('/getYearMonth').then(res=> {
            console.log('getYearMonth',res);
            setValues(oldValues => ({
                ...oldValues,
                fileList:res.data.msg
            }));
        })
    },[]);
    

    effect函数会在浏览器完成画面渲染之后延迟调用

    在一个hooks函数中,可以同时存在多个effect函数,所以,当有需求每次更新都执行useEffect中的代码时,可以用一个useEffect请求数据,用其他的useEffect做另外的事情。只需根据第二个参数即可区别不同作用。

    //官方示例性能优化
    useEffect(() => {
      document.title = `You clicked ${count} times`;
    }, [count]); // 仅在 count 更改时更新
    

    三:userEffect实现componentWillUnmount 回目录

    部分情况下,需要在组件卸载是做一些事情,例如移除监听事件等,在class组件中,我们可以在componentWillUNmount这个生命周期中做这些事情,而在hooks中,我们可以通过useEffect第一个函数参数中返回一个函数来实现相同效果。

    // 官方示例
    useEffect(() => {
        // ...
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        return () => {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
    });
    

    个人示例:

    import React, { useState, useEffect } from 'react';
    import { Switch, Route, Link } from 'react-router-dom';
    
    function Index() {
      useEffect(() => {
        console.log('useEffect:come-index');
        return () => {
          console.log('useEffect:leave-index');
        };
      }, []);
      return <div>这是首页</div>;
    }
    
    function List() {
      useEffect(() => {
        console.log('useEffect:come-list');
        return () => {
          console.log('useEffect:leave-list');
        };
      }, []);
      return <div>这是列表页</div>;
    }
    
    function HooksEffect() {
      const [count, setCount] = useState(0);
      useEffect(() => {
        console.log(count);
        return () => {
          console.log('-------------------');
        };
      }, [count]);
    
      return (
        <div>
          <p>你点击了{count}次</p>
          <button 
            type="submit" 
            onClick={() => { setCount(count + 1); }}
          >
            点击+1
          </button>
          <ul>
            <li><Link to="/index">首页</Link></li>
            <li><Link to="/list">列表</Link></li>
          </ul>
          <Switch>
            <Route path="/index" exact component={Index} />
            <Route path="/list" component={List} />
          </Switch>
        </div>
      );
    }
    
    export default HooksEffect;
    

    在上面的例子中,全部用了清除副作用的return 函数,其中,hooksEffect组件为父组件,listindex为子组件,如果在子组件的useEffect中不使用第二个参数空数组,则父组件的每次更新都会引发子组件的useEffect的调用,在父组件的useEffect函数中,第二个参数数组中为count,代表每次count的变化都会引起useEffect函数的触发以及返回函数的调用。

    四:父子组件传值 回目录

    父子组件传值在实际开发中是必不可少的,在class组件中,我们可以直接给子组件添加属性,然后在子组件通过props即可获取到父组件的值。但是在hooks中,组件都是函数,没有props,所以不能用相同的方式传值

    hooks中,组件都是函数,所以我们可以通过参数的方式进行传值,也可以通过content来进行传值,这一小节主要是讲通过参数方式进行传值,案例如下:

    import React, { useState } from 'react';
    
    function Show({ count, age, clear }) {
      return (
        <div>
          数量:{count}  
          年龄:{age} 
          <button 
            type="button" 
            onClick={() => { clear(); }}
          >
            复原
          </button>
        </div>
      );
    }
    
    function HooksContext() {
      const [count, setCnt] = useState(0);
      const [age, setAge] = useState(16);
      function clear() {
        setCnt(0);
        setAge(16);
      }
      return (
        <div>
          <p>小女子芳年{age}</p>
          <p>你点击了{count}次</p>
          <button 
            type="button" 
            onClick={() => { setCnt(count + 1); 
            setAge(age + 1); }}
          >
            点击+1
          </button>
          <Show count={count} age={age} clear={clear} />
        </div>
      );
    }
    
    export default HooksContext;
    

    在上面的案例中,通过给Show组件属性赋值,然后在Show函数组件中以解构参数的方式获取父组件的值。这种传值方式和类组件本质上还是一样的。

    五:userContext 回目录

    使用userContext,不仅可以实现父子组件传值,还可以跨越多个层级进行传值,例如父组件可以给孙子组件甚至重孙子组件进行直接传值等,redux全局状态管理本质上也是对content的一种应用。

    hooks中使用content,需要使用createContextuseContext,废话不多说,直接示例展示用法

    // context.js  新建一个context
    import { createContext } from 'react';
    
    const ShowContext = createContext('aaa');
    
    export default ShowContext;
    
    // HooksContext.jsx  父组件,提供context
    import React, { useState } from 'react';
    import Show from './Show.jsx';
    import ShowContext from './context';
    
    function HooksContext() {
      const [count, setCnt] = useState(0);
      const [age, setAge] = useState(16);
      function clear() {
        setCnt(0);
        setAge(16);
      }
      return (
        <div>
          <p>小女子芳年{age}</p>
          <p>你点击了{count}次</p>
          <button
            type="button"
            onClick={() => { setCnt(count + 1); setAge(age + 1); }}
          >
            点击+1
          </button>
          <ShowContext.Provider value={{ count, age, clear }}>
            <Show />
          </ShowContext.Provider>
        </div>
      );
    }
    
    export default HooksContext;
    
    // Show.jsx  子组件,使用context
    import React, { useContext } from 'react';
    import ShowContext from './context';
    
    function Show() {
      const { count, age, clear } = useContext(ShowContext);
      return (
        <div>
          数量:{count}
          年龄:{age}
          <button
            type="button"
            onClick={() => { clear(); }}
          >
            复原
          </button>
        </div>
      );
    }
    
    export default Show;
    
    

    上面是一个完整的使用content实现父子组件传值的过程,如果Show组件下还有子组件,无论多少层,都可以用useContext直接取到HooksContext父组件提供的值,而context.js文件是新建一个context,新建必须要单独列出来,否则子组件无法使用useContext

    content提供了一种树状结构,被Context.Provider所包裹的所有组件,都可以直接取数据。redux就是利用了context的这种特性实现全局状态管理。在下面的几小节中,我们会讲hookscontext搭配useReducer来实现redux的功能。

    六:userReducer 回目录

    userReduceruseState的替代方案,它接收一个形如(state,action) => newStatereducer,并返回当前的state以及其配套的dispatch方法。
    总的来说呢,userReducer可以接受两个参数,第一个参数就是和redux中的reducer一样的纯函数,第二个参数是state的初始值,并返回当前state以及dispatch

    还是以官方示例的计数器为例

    import React, { useReducer } from 'react';
    
    function countReducer(state, action) {
      switch (action.type) {
        case 'add':
          return state + 1;
        case 'minus':
          return state - 1;
        default:
          return state;
      }
    }
    
    function HooksEffect() {
      const [count, dispatch] = useReducer(countReducer, 0);
    
      return (
        <div>
          <p>你点击了{count}次</p>
          <button
            type="button"
            onClick={() => { dispatch({ type: 'add' }); }}
          >
            点击+1
          </button>
          <button
            type="button"
            onClick={() => { dispatch({ type: 'minus' }); }}
          >
            点击-1
          </button>
        </div>
      );
    }
    
    export default HooksEffect;
    
    

    相比起redux还需要connect高阶函数包裹一下才能将dispatch和state注入到props中,hooks中使用reducer更加简洁。在下面一小节中,我们会用案例来实现redux。

    七:useReducer替代Redux案例 回目录

    在本小节中,我们会用contextuseReducer来实现redux的效果。依然是使用计数器这个功能,先贴代码,后面会详细讲解:

    // count.js  定义context和reducer,导出context和包含reducer的context包裹组件。
    import React, { createContext, useReducer } from 'react';
    
    function countReducer(state, action) {
      switch (action.type) {
        case 'add':
          return state + 1;
        case 'minus':
          return state - 1;
        default:
          return state;
      }
    }
    
    const ADDCOUNT = 'add';
    const MINUSCOUNT = 'minus';
    
    export const CountContext = createContext();
    export const CountWrap = (props) => {
      const [count, dispatch] = useReducer(countReducer, 0);
      return (
        <CountContext.Provider
          value={{ count, dispatch, ADDCOUNT, MINUSCOUNT }}
        >
          {props.children}
        </CountContext.Provider>
      );
    };
    
    // ReducerToRedux.jsx,连接组件,
    import React from 'react';
    import Button from './Button';
    import Show from './Show';
    import { CountWrap } from './count';
    
    function ReducerToRedux() {
      return (
        <div>
          <CountWrap>
            <Show />
            <Button />
          </CountWrap>
        </div>
      );
    }
    
    export default ReducerToRedux;
    
    // Show.jsx  显示当前数值的组件
    import React, { useContext } from 'react';
    import { CountContext } from './count';
    
    function ReducerToRedux() {
      const { count } = useContext(CountContext);
      return (
        <div>现在的计数器值为:{count}</div>
      );
    }
    
    export default ReducerToRedux;
    
    // Button.jsx  按钮组件,可以实现计数器的增和减
    import React, { useContext } from 'react';
    import { CountContext } from './count';
    
    function ReducerToRedux() {
      const { dispatch, ADDCOUNT, MINUSCOUNT } = useContext(CountContext);
      return (
        <div>
          <button
            type="button"
            onClick={() => { dispatch({ type: MINUSCOUNT }); }}
          >点我-1</button>
          <button
            type="button"
            onClick={() => { dispatch({ type: ADDCOUNT }); }}
          >点我+1</button>
        </div>
      );
    }
    
    export default ReducerToRedux;
    

    通过reducer和context实现计数器的功能,我们共用了四个文件,当然count.js这个文件本应该拆分成三个文件,常量单独定义一个文件,reducer纯函数也应该单独定一个文件,不过代码不多,就暂时合一块了。

    count.js中,我们导出CountContextCountWrap,其中,CoutWrap就是provider,也就是只要被CountWrap包裹过的组件,就可以使用userContent取到传递数据,而CountContext就是用createContext新建的一个content,使用useContext取传递数据的时候会用到。同时,在这个文件中,我们还将从useReducer解构出的countdispatch,以及常量增减通过provider传递给包裹组件,使被包裹的组件可以通过useContext取到这些数据。函数countReducer就是和redux中的reducer一样的纯函数,子组件dispatch action,reducer则是接受当前stateaction,通过判断action,返回新的state

    Button组件中,我们通过countContext取到dispatch及常量,改变count这个数值,在Show组件中,只是展示count,在ReducerToRedux文件中,是做一个连接器,用CountWrap包裹ButtonShow组件。

    可能说的有些啰嗦,看上去有些复杂,其实稍一整理,原理很简单,自己写一遍整理清楚逻辑使用上就很简单了。

    讲到这里,其实hooks已经能应对绝大部分场景了,下面两小节,我们会讲一下useMemouseRef,用于优化渲染以及处理特殊情况。

    八:useMemo 回目录

    把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

    useMemo是函数式组件官方提供的性能优化的一个方法,接受两个参数,第一个参数是要执行的函数,第二个参数是state中的值或者父组件传下来的值,代表只有当第二个参数的值发生变化时,才执行函数。其中,第二个参数是数组,可以同时优化多个state或者父组件传下来的参数,首次渲染组件是,如果页面用到要优化的值,函数会执行。

    我们还是以计数器以及年龄为例

    import React, { useState, useMemo } from 'react';
    
    function Show({ count, age, clear }) {
      function ageChange(value) {
        console.log(value);
        return value + 2;
      }
      const myAge = useMemo(() => ageChange(age), [age]);
      return (
        <div>
          数量:{count}  我的年龄:{myAge}
          <button
            type="button"
            onClick={() => { clear(); }}
          >复原</button>
        </div>
      );
    }
    
    function HooksUseMome() {
      const [count, setCnt] = useState(0);
      const [age, setAge] = useState(16);
      function clear() {
        setCnt(0);
        setAge(16);
      }
      return (
        <div>
          <p>小女子芳年{age}</p>
          <p>你点击了{count}次</p>
          <button
            type="button"
            onClick={() => { setAge(age + 1); }}
          >点击年龄+1</button>
          <button
            type="button"
            onClick={() => { setCnt(count + 1); }}
          >点击计数器+1</button>
          <Show count={count} age={age} clear={clear} />
        </div>
      );
    }
    
    export default HooksUseMome;
    
    

    在上面的例子中,父组件小女子初始年龄为16岁,而到子组件经过ageChange函数,返回我的年龄永远比小女子年龄大两岁。

    但是如果没有useMemo,当父组件的计数器count值发生变化时,子组件的ageChange函数也会执行,这不是我们想要的结果,我们只想当小女子的年龄发生变化时,再执行ageChange函数。所以,用useMemo可以实现我们想要的效果。如上面代码所示const myAge = useMemo(() => ageChange(age), [age]);,使用useMemo,第二个参数是age,这样,只有当age发生变化时,才执行其中的函数。

    在类组件中,有shouldComponentDidUpdata生命周期,我们可以在其中做监测,当检测到state值没发生变化时,直接不渲染组件,而useMemo和这个生命周期还有些许不同。它是当检测的state发生变化时而执行某些函数,避免额外的开销,节省性能。

    九: useRef 回目录

    在项目开发中,我们比较少用到ref,一般我们不直接操作DOM,都是通过状态来控制DOM,不过在某些情况下,可能还是会用到ref,这一节我们通过对input输入框数据的双向绑定来认识useRef

    import React, { useState, useRef } from 'react';
    
    function HooksUseRef() {
      const [inputValue, setInputValue] = useState();
      const inputRef = useRef(null);
    
      function inputChangeHandle(e) {
        setInputValue(e.target.value);
      }
    
      function inputRefChangeHandle() {
        console.log(inputRef.current.value);
      }
      return (
        <div>
          <div>
            <input
              value={inputValue}
              onChange={inputChangeHandle}
              type="text"
            />
            <span>使用state绑定inputValue值</span>
          </div>
          <div>
            <input
              ref={inputRef}
              onChange={inputRefChangeHandle}
              type="text"
            />
            <span>使用Ref绑定inputValue值</span>
          </div>
        </div>
      );
    }
    
    export default HooksUseRef;
    
    

    在上面的案例中,我们如果要取input的值,如果是state双向绑定,可以直接取inputValue,如果是用ref,则可以通过inputRef.current.value取到值
    通过const inputRef = useRef(null);,我们获取到的是一个对象,而current属性就是其中的dom元素。

    十: useCallBack 回目录

    把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

    简而言之,useCallBack是用来缓存函数的,在class类中,我们通常在constructor中使用this.fn = this.fn.bind(this)来绑定this,是每次调用的fn都是之前的fn,而不用开辟新的函数。而useCallback同样有此功能,useCallBackuseMemo的不同点在于useMemo相当于缓存state,而useCallBack相当于缓存函数,官方给的解释是这样的useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。.

    我们下面还是用计数器和年龄做例子

    import React, { useState, useEffect, useCallback } from 'react';
    
    function Show({ countCallBack, ageCallBack }) {
      const [count, setCount] = useState(() => { countCallBack(); });
      const [age, setAge] = useState(() => { ageCallBack(); });
    
      useEffect(() => {
        setCount(countCallBack());
      }, [countCallBack]);
    
      useEffect(() => {
        setAge(ageCallBack());
      }, [ageCallBack]);
    
      return (
        <div>
          数量:{count}  年龄:{age}
        </div>
      );
    }
    
    function HooksCallBack() {
      const [count, setCnt] = useState(0);
      const [age, setAge] = useState(16);
    
      const countCallBack = useCallback(() => {
        return count;
      }, [count]);
    
      const ageCallBack = useCallback(() => {
        return age;
      }, []);
    
      return (
        <div>
          <p>小女子芳年{age}</p>
          <p>你点击了{count}次</p>
          <button
            type="button"
            onClick={() => { setAge(age + 1); }}
          >点击年龄+1</button>
          <button
            type="button"
            onClick={() => { setCnt(count + 1); }}
          >点击计数器+1</button>
          <Show countCallBack={countCallBack} ageCallBack={ageCallBack} />
        </div>
      );
    }
    
    export default HooksCallBack;
    
    

    在上面的例子中,只有点击计数器按钮,子组件才会跟着更新,点击年龄按钮子组件则不跟着更新。使用useCallback如果没有依赖,则只会执行一次,只有依赖改变,才会返回新的函数,我们可以根据这个规则实现bind的效果。

    十一: 自定义函数 回目录

    这一小节,我们做一个监听浏览器窗口的自定义函数,废话不多说,直接上例子:

    import React, { useState, useEffect, useCallback } from 'react';
    
    function useWinSize() {
      const [size, setSize] = useState({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      });
    
      const resizeHandle = useCallback(() => {
        setSize({
          width: document.documentElement.clientWidth,
          height: document.documentElement.clientHeight
        });
      }, []);
    
      useEffect(() => {
        window.addEventListener('resize', resizeHandle);
        return () => {
          window.removeEventListener('resize', resizeHandle);
        };
      }, []);
      return size;
    }
    
    function HooksFunction() {
      let size = useWinSize();
      return (
        <div>
          浏览器窗口尺寸{`${size.width}*${size.height}`}
        </div>
      );
    }
    
    export default HooksFunction;
    
    
    

    上面的代码就不多解释了,所需要注意的是自定义函数需要以use开头,且后面应该用大写字母与use分隔开。到此呢,hooks先写到这里,基本上也能面对绝大多数的业务场景,其它的hooksAPI等以后开发中如果有用到,再来补充。

    在下一节中,我们将会把TS从基础到项目应用整个的梳理出来,分两篇来完成,待续。。。

    展开全文
  • 什么是Hooks

    2020-06-06 11:12:17
    什么是Hooks Hooks是一个新的React特性提案,组件尽量写成纯函数,如果需要外部React特性(比如状态管理,生命周期),就用钩子把外部特性"钩"进来,通常函数名字都是以use开头。首次在v16.7.0-alpha版本中添加,在...

    什么是Hooks

    Hooks是一个新的React特性提案,组件尽量写成纯函数,如果需要外部React特性(比如状态管理,生命周期),就用钩子把外部特性"钩"进来,通常函数名字都是以use开头。首次在v16.7.0-alpha版本中添加,在v16.8.0中正式发布

    Hooks产生的背景

    • 跨组件复用stateful logic十分困难 使用Hooks,你可以在将含有state的逻辑从组件中抽象出来,这将可以让这些逻辑容易被测试。同时,Hooks可以帮助你在不重写组件结构的情况下复用这些逻辑。
    • 复杂的组件难以理解 Hooks允许您根据相关部分(例如设置订阅或获取数据)将一个组件分割成更小的函数,而不是强制基于生命周期方法进行分割
    • 不止是用户,机器也对Classes难以理解 Hooks让你可以在classes之外使用更多React的新特性

    常用Hooks

    • useState
    let [count, setCount] = useState(100);
    
    • useEffect
    useEffect(()=>{
        return ()=>{}
    }, [])
    

    在这里插入图片描述

    - useLayoutEffect

    1. useEffect 在全部渲染完毕后才会执行
    2. useLayoutEffect 会在 浏览器 layout 之后,painting 之前执行
    3. 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
    4. 可以使用它来读取 DOM 布局并同步触发重渲染
    5. 在浏览器执行绘制之前 useLayoutEffect 内部的更新计划将被同步刷新
    6. 会阻塞页面的渲染,如果在里面执行耗时任务的话,页面就会卡顿

    useContext 直接获取祖先元素通过createContext创建的context

    • useReducer
    const [state, dispatch] = useReducer(reducer, initialState, init);
    
    • memo
    • useMemo
    • useCallback
    //memo,useMemo和useCallback在优化组件的应用场景
    1.在子组件不需要父组件值和函数的情况下,使用memo包裹即可
    2.如果是函数作为props,可以使用useCallback保证不会反复修改
    3.如果是值作为props,可以使用useMemo保证值不会反复修改
    
    • useRef
    不符合capture values,本身不会变化,存储的.current会变化
    
    • useImperativeHandle
    配合forwardRef使用,用于自定义通过ref给父组件暴露的值
    
    • useDebugValue
    用于开发者工具调试
    

    封装自定义Hooks

    1.封装hooks获取窗口大小

    // useWinSize
    import React, { useState ,useEffect ,useCallback } from 'react';
    
    export default function useWinSize(){
        const [ size , setSize] = useState({
            width:document.documentElement.clientWidth,
            height:document.documentElement.clientHeight
        })
     
        const onResize = useCallback(()=>{
            setSize({
                width: document.documentElement.clientWidth,
                height: document.documentElement.clientHeight
            })
        },[])
    
        useEffect(()=>{
            window.addEventListener('resize',onResize)
            return ()=>{
                window.removeEventListener('resize',onResize)
            }
        },[])
     
        return size;
    }
    
    // demo.js
    import useWinSize from './useWinSize'
    
    export default function(){
        const size = useWinSize()
        return (
            <div>页面Size:{size.width}x{size.height}</div>
        )
    }
    

    2.延迟设置值

    import React, {useState, useRef, useEffect} from 'react'
    
    export const useDelayState = (initialState)=>{
        const [state, setState] = useState(initialState);
        const ref = useRef();
        const delaySetState = (value, delay)=>{
            ref.current = setTimeout(()=>{
                setState(value)
            }, delay)
        }
        useEffect(()=>{
            return ()=>{
                clearTimeout(ref.current)
            }
        }, [])
        return [state, delaySetState, setState]
    }
    

    3.GeoLocation获取地理定位

    import { useEffect, useState } from 'react';
    
    const useGeolocation = options => {
      const [state, setState] = useState({
        loading: true,
        accuracy: null,
        altitude: null,
        altitudeAccuracy: null,
        heading: null,
        latitude: null,
        longitude: null,
        speed: null,
        timestamp: Date.now(),
      });
      let mounted = true;
      let watchId: any;
    
      const onEvent = event => {
        if (mounted) {
          setState({
            loading: false,
            accuracy: event.coords.accuracy,
            altitude: event.coords.altitude,
            altitudeAccuracy: event.coords.altitudeAccuracy,
            heading: event.coords.heading,
            latitude: event.coords.latitude,
            longitude: event.coords.longitude,
            speed: event.coords.speed,
            timestamp: event.timestamp,
          });
        }
      };
      const onEventError = (error) =>
        mounted && setState(oldState => ({ ...oldState, loading: false, error }));
    
      useEffect(() => {
        navigator.geolocation.getCurrentPosition(onEvent, onEventError, options);
        watchId = navigator.geolocation.watchPosition(onEvent, onEventError, options);
    
        return () => {
          mounted = false;
          navigator.geolocation.clearWatch(watchId);
        };
      }, []);
    
      return state;
    };
    export default useGeolocation;
    
    展开全文
  • Hooks

    2020-10-10 08:22:16
    目录 useState userEffect userEffect实现componentWillUnmont 父子组件传值 userContext userReducer useReducer替代Redux案例 useMemo useRef useCallBack ...在组件中,我们难免使用state来进行数据的实时响应,这是...
  • 深入浅出HOOKS(上)

    2007-04-08 11:06:00
    2004-2-18加入 来自未知 作者佚名 1条评论 点击3585次 HOOKS 说明书hook是WINDOWS提供的一种消息处理机制,它使得程序员可以使用子过程来监视系统消息,并在消息到达目标过程前得到处理。下面将介绍WINNDOWS ...
  • 了解Webhooks

    千次阅读 2017-12-08 11:02:41
    前言Webhooks就是用户通过自定义回调函数的方式来改变Web应用的一种行为,这些回调函数可以由不是该Web应用官方的第三方用户或者开发人员来维护,修改。用一个比喻来说明,在js中,target.addEventListener(type, ...
  • hooks常见问题

    2019-08-01 08:49:28
    1.用了hooks后生命周期函数放哪里 function App () { useEffect( ()=>{ //等同于componetDidMount } ) return () => { //等同于componentDidWillUnmount } } 2.类实例成员变量映射到hooks class ...
  • 初步了解hooks用法

    千次阅读 2019-09-25 14:20:09
    了解 react hooks React hooks在16.7的测试版到16.8的正式上线以后到现在的火热。可以看出react hooks新特性的功能是效果绝佳的,而这种模式也被Vue3.0所参考。 React hooks的出现使函数式组件也可以像类组件一样...
  • Vue-hooks(钩子)

    万次阅读 2018-11-05 15:35:28
    vue-hooks 最近尤大发布了一个最新的npm包:vue-hooks 。 既喜且优,真是一个悲伤的故事。 这个npm包是关于hook(钩子)一个最新思路的探索,也是react团队前不久的首创… hooks 是什么 Hook是react中得一项新功能...
  • HOOKS的详细介绍!

    千次阅读 2013-08-21 15:40:47
    下面将介绍WINNDOWS HOOKS并且说明如何在WINDOWS 程序中使用它。   =========================关于HOOKS====================== 使用HOOK 将会降低系统效率,因为它增加了系统处量消息的工作量。建议
  • 一、Hooks组件相比于Class组件二、Hooks组件相比于高阶组件 React Hooks相对高阶组件和Class组件有什么优势/缺点? 一、Hooks组件相比于Class组件 首先我认为hooks组件其实是降低了react开发的使用难度的,让新手...
  • Erro: Invalid hook ... Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and t...
  • 但是commit 失败,报error: cannot run .git/hooks/pre-commit: No such file or directory 查看仓库 .git/hooks/pre-commit文件也存在,折腾了很久 发现执行如下命令后就OK了cp .git/hooks/pre-commit /tmp/pre-c
  • Tapable.plugin is deprecated. Use new API on `.hooks` instead

    万次阅读 多人点赞 2018-07-29 11:27:21
    问题描述 在使用extract-text-webpack-plugin给webpack打包时... Use new API on `.hooks` instead 问题原因 extract-text-webpack-plugin目前版本不支持webpack4。 解决方案 使用extract-text-we...
  • 配置hooks的eslint-plugin-react-hooks

    千次阅读 2019-11-07 19:58:53
    eslint-plugin-react-hooks配置 在使用create-react-app脚手架生成项目后,使用hooks特性没有eslint提示报错信息。可安装 eslint-plugin-react-hooks并配置package.json中的eslintConfig即可 安装 npm install ...
  • import ReactDOM from "react-dom" import store from "./store" import {Provider} from "react-redux" import Todo from "./components/Todo" const APP = ( <Provider store={store}>...
  • React Hooks中父组件中调用子组件方法

    千次阅读 2019-09-27 16:07:43
    React Hooks中父组件中调用子组件方法 场景: 在使用antd的Table组件时,对Table子组件进行了二次封装(<AsyncTable />);在父组件(<f-comp />)中需要调用<AsyncTable />中的一个 函数,对antd...
  • 当我们在Xcode中使用CocoaPods执行Install Pods时,在终端会输出这个提示信息ruby_executable_hooks路径未找到;/usr/local/bin/pod install env: ruby_executable_hooks: No such file or directory解决办法打开我们...
1 2 3 4 5 ... 20
收藏数 46,301
精华内容 18,520
关键字:

hooks