zoukankan      html  css  js  c++  java
  • React Hooks 使用指南

    一、组件类的缺点

    React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class)。下面是一个简单的组件类。

    import React, { Component } from "react";
    
    export default class Button extends Component {
      constructor() {
        super();
        this.state = { buttonText: "Click me, please" };
        this.handleClick = this.handleClick.bind(this);
      }
      handleClick() {
        this.setState(() => {
          return { buttonText: "Thanks, been clicked!" };
        });
      }
      render() {
        const { buttonText } = this.state;
        return <button onClick={this.handleClick}>{buttonText}</button>;
      }
    }

    这个组件类仅仅是一个按钮,但可以看到,它的代码已经很"重"了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂。

    Redux 的作者 Dan Abramov 总结了组件类的几个缺点:

    • 大型组件很难拆分和重构,也很难测试。
    • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
    • 组件类引入了复杂的编程模式,比如 render props 和高阶组件。

    二、函数组件

    React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。

    React 早就支持函数组件,下面就是一个例子。

    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }

    但是,这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。

    React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

    三、Hook 的含义

    Hook 这个单词的意思是"钩子"。

    React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。

    你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。

    所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。

    下面介绍 React 默认提供的四个最常用的钩子。

    • useState()
    • useContext()
    • useReducer()
    • useEffect()

    四、useState():状态钩子

    useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

    本文前面那个组件类,用户点击按钮,会导致按钮的文字改变,文字取决于用户是否点击,这就是状态。使用useState()重写如下。

    import React from "react";
    import ReactDOM from "react-dom";
    
    import Button from './button.js';
    import "./styles.css";
    
    function App() {
      return (
        <div className="App">
          <Button/>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

    上面代码中,Button 组件是一个函数,内部使用useState()钩子引入状态。

    useState()这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是buttonText),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名(上例是setButtonText)。

    • 注意事项1: 不可局部更新

    如果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>
        )
    }
    • 注意事项2: 地址要变

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

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

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

    五、useContext():共享状态钩子

    如果需要在组件之间共享状态,可以使用useContext()

    现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。

    <div className="App">
      <Navbar/>
      <Messages/>
    </div>

    第一步就是使用 React Context API,在组件外部建立一个 Context。

    const AppContext = React.createContext({});

    组件封装代码如下。

    <AppContext.Provider value={{
      username: 'superawesome'
    }}>
      <div className="App">
        <Navbar/>
        <Messages/>
      </div>
    </AppContext.Provider>

    上面代码中,AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享。

    Navbar 组件的代码如下。

    const Navbar = () => {
      const { username } = useContext(AppContext);
      return (
        <div className="navbar">
          <p>AwesomeSite</p>
          <p>{username}</p>
        </div>
      );
    }

    上面代码中,useContext()钩子函数用来引入 Context 对象,从中获取username属性。

    Message 组件的代码也类似。

    const Messages = () => {
      const { username } = useContext(AppContext)
    
      return (
        <div className="messages">
          <h1>Messages</h1>
          <p>1 message for {username}</p>
          <p className="message">useContext is awesome!</p>
        </div>
      )
    }

    代码总览:

    一、使用 AppContext= createContext(initial) 创建上下文

    二、使用 <AppContext.Provider> 圈定作用域

    三、在作用域内使用 useContext(AppContext)来使用上下文

    import React, { useContext } from "react";
    import ReactDOM from "react-dom";
    import "./styles.css";
    
    const AppContext = React.createContext({});
    
    const Navbar = () => {
      const { username } = useContext(AppContext)
    
      return (
        <div className="navbar">
          <p>AwesomeSite</p>
          <p>{username}</p>
        </div>
      )
    }
    
    const Messages = () => {
      const { username } = useContext(AppContext)
    
      return (
        <div className="messages">
          <h1>Messages</h1>
          <p>1 message for {username}</p>
          <p className="message">useContext is awesome!</p>
        </div>
      )
    }
    
    function App() {
      return (
        <AppContext.Provider value={{
          username: 'superawesome'
        }}>
          <div className="App">
            <Navbar />
            <Messages />
          </div>
        </AppContext.Provider>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

    六、useReducer():action 钩子

    React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。

    Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState

    useReducers()钩子用来引入 Reducer 功能。

    const [state, dispatch] = useReducer(reducer, initialState);

    上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。

    下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。

    const myReducer = (state, action) => {
      switch(action.type)  {
        case('countUp'):
          return  {
            ...state,
            count: state.count + 1
          }
        default:
          return  state;
      }
    }

    组件代码如下。

    function App() {
      const [state, dispatch] = useReducer(myReducer, { count:   0 });
      return  (
        <div className="App">
          <button onClick={() => dispatch({ type: 'countUp' })}>
            +1
          </button>
          <p>Count: {state.count}</p>
        </div>
      );
    }

    代码总览:

    看代码,分四步走

    一、创建初始值initialState  ({ count: 0 })

    二、创建所有操作reducer(state, action);

    三、传给userReducer,得到读和写API

    四、调用写({type: '操作类型'})

    总的来说,useReducer 是 useState 的复杂版

    import React, { useReducer } from "react";
    import ReactDOM from "react-dom";
    import "./styles.css";
    
    const myReducer = (state, action) => {
      switch(action.type) {
        case('countUp'):
          return {
            ...state,
            count: state.count + 1
          }
        default:
          return state
      }
    }
    
    function App() {
      const [state, dispatch] = useReducer(myReducer, { count: 0 })
    
      return (
        <div className="App">
          <button onClick={() => dispatch({ type: 'countUp' })}>
            +1
          </button>
          <p>Count: {state.count}</p>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

    如何代替 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);
        });
    }

    由于 Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel),如果你需要这两个功能,还是要用 Redux。

    七、useEffect():副作用钩子

    useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()

    useEffect()的用法如下。

    1、作为 componentDidMount 使用,[ ] 作第二个参数

    2、作为 componentDidUpdate 使用,可指定依赖

    3、作为 componentWillUnmount 使用,通过 return

    4、以上三种用途可同时存在

    useEffect(()  =>  {
      // Async Action
    }, [dependencies])

    上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()

    下面看一个例子。

    const Person = ({ personId }) => {
      const [loading, setLoading] = useState(true);
      const [person, setPerson] = useState({});
    
      useEffect(() => {
        setLoading(true); 
        fetch(`https://swapi.co/api/people/${personId}/`)
          .then(response => response.json())
          .then(data => {
            setPerson(data);
            setLoading(false);
          });
      }, [personId])
    
      if (loading === true) {
        return <p>Loading ...</p>
      }
    
      return <div>
        <p>You're viewing: {person.name}</p>
        <p>Height: {person.height}</p>
        <p>Mass: {person.mass}</p>
      </div>
    }

    上面代码中,每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。如果同时存在多个 useEffect, 会按照出现次序执行。

    代码总览

    import React, { useState, useEffect } from "react";
    import ReactDOM from "react-dom";
    import "./styles.css";
    
    const Person = ({ personId }) => {
      const [loading, setLoading] = useState(true);
      const [person, setPerson] = useState({});
    
      useEffect(() => {
        setLoading(true);
        fetch(`https://swapi.co/api/people/${personId}/`)
          .then(response => response.json())
          .then(data => {
            setPerson(data);
            setLoading(false);
          });
      }, [personId]);
    
      if (loading === true) {
        return <p>Loading ...</p>;
      }
    
      return (
        <div>
          <p>You're viewing: {person.name}</p>
          <p>Height: {person.height}</p>
          <p>Mass: {person.mass}</p>
        </div>
      );
    };
    
    function App() {
      const [show, setShow] = useState("1");
    
      return (
        <div className="App">
          <Person personId={show} />
          <div>
            Show:
            <button onClick={() => setShow("1")}>Luke</button>
            <button onClick={() => setShow("2")}>C-3PO</button>
          </div>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

    八、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.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])

    useRef:

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

    useRef这个hooks函数,除了传统的用法之外,它还可以“跨渲染周期”保存数据。

    传统用法:

    import React, { useState, useEffect, useMemo, useRef } from 'react';
    
    export default function App(props){
      const [count, setCount] = useState(0);
    
      const doubleCount = useMemo(() => {
        return 2 * count;
      }, [count]);
    
      const couterRef = useRef();
    
      useEffect(() => {
        document.title = `The value is ${count}`;
        console.log(couterRef.current);
      }, [count]);
      
      return (
        <>
          <button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
        </>
      );
    }

    代码中用useRef创建了couterRef对象,并将其赋给了buttonref属性。这样,通过访问couterRef.current就可以访问到button对应的DOM对象。

    然后再来看看它保存数据的用法。

    在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state。没错,一个组件的state可以在多次渲染之后依旧不变。但是,state的问题在于一旦修改了它就会造成组件的重新渲染。

    那么这个时候就可以使用useRef来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染。

    import React, { useState, useEffect, useMemo, useRef } from 'react';
    
    export default function App(props){
      const [count, setCount] = useState(0);
    
      const doubleCount = useMemo(() => {
        return 2 * count;
      }, [count]);
    
      const timerID = useRef();
      
      useEffect(() => {
        timerID.current = setInterval(()=>{
            setCount(count => count + 1);
        }, 1000); 
      }, []);
      
      useEffect(()=>{
          if(count > 10){
              clearInterval(timerID.current);
          }
      });
      
      return (
        <>
          <button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
        </>
      );
    }

    在上面的例子中,我用ref对象的current属性来存储定时器的ID,这样便可以在多次渲染之后依旧保存定时器ID,从而能正常清除定时器。

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

    十、创建自己的 Hooks

    上例的 Hooks 代码还可以封装起来,变成一个自定义的 Hook,便于共享。

    const usePerson = (personId) => {
      const [loading, setLoading] = useState(true);
      const [person, setPerson] = useState({});
      useEffect(() => {
        setLoading(true);
        fetch(`https://swapi.co/api/people/${personId}/`)
          .then(response => response.json())
          .then(data => {
            setPerson(data);
            setLoading(false);
          });
      }, [personId]);  
      return [loading, person];
    };

    上面代码中,usePerson()就是一个自定义的 Hook。

    Person 组件就改用这个新的钩子,引入封装的逻辑。

    const Person = ({ personId }) => {
      const [loading, person] = usePerson(personId);
    
      if (loading === true) {
        return <p>Loading ...</p>;
      }
    
      return (
        <div>
          <p>You're viewing: {person.name}</p>
          <p>Height: {person.height}</p>
          <p>Mass: {person.mass}</p>
        </div>
      );
    };

    代码总览:

    import React, { useState, useEffect } from "react";
    import ReactDOM from "react-dom";
    import "./styles.css";
    
    const usePerson = personId => {
      const [loading, setLoading] = useState(true);
      const [person, setPerson] = useState({});
      useEffect(() => {
        setLoading(true);
        fetch(`https://swapi.co/api/people/${personId}/`)
          .then(response => response.json())
          .then(data => {
            setPerson(data);
            setLoading(false);
          });
      }, [personId]);
      return [loading, person];
    };
    
    const Person = ({ personId }) => {
      const [loading, person] = usePerson(personId);
    
      if (loading === true) {
        return <p>Loading ...</p>;
      }
    
      return (
        <div>
          <p>You're viewing: {person.name}</p>
          <p>Height: {person.height}</p>
          <p>Mass: {person.mass}</p>
        </div>
      );
    };
    
    function App() {
      const [show, setShow] = useState("1");
    
      return (
        <div className="App">
          <Person personId={show} />
          <div>
            Show:
            <button onClick={() => setShow("1")}>Luke</button>
            <button onClick={() => setShow("2")}>C-3PO</button>
          </div>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

     贴心例子:

    // 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>
        )
    }

    参考链接:

    http://www.ruanyifeng.com/blog/2019/09/react-hooks.html

    https://www.jianshu.com/p/1252be39c702

  • 相关阅读:
    性能测试工具LoadRunner19-LR之Controller IP欺骗
    JavaScript—06数组与函数
    JavaScript—05流程控制与代码规范要求
    JavaScript—04运算符
    JS做简单的留言板
    JavaScript—03 变量与数据类型
    JavaScript—02 JS组成及注释等
    JavaScript—01汇编语言和计算机基础脑图
    01移动端布局基础-脑图
    解决粘包-复杂版
  • 原文地址:https://www.cnblogs.com/art-poet/p/14039345.html
Copyright © 2011-2022 走看看