在没风的地方找太阳  在你冷的地方做暖阳 人事纷纷  你总太天真  往后的余生  我只要你 往后余生  风雪是你  平淡是你  清贫也是你 荣华是你  心底温柔是你  目光所致  也是你 想带你去看晴空万里  想大声告诉你我为你着迷 往事匆匆  你总会被感动  往后的余生  我只要你 往后余生  冬雪是你  春花是你  夏雨也是你 秋黄是你  四季冷暖是你  目光所致  也是你 往后余生  风雪是你  平淡是你  清贫也是你 荣华是你  心底温柔是你  目光所致  也是你
jQuery火箭图标返回顶部代码 - 站长素材

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

posted @ 2020-11-26 16:49  艺术诗人  阅读(308)  评论(0编辑  收藏  举报