用实战带你走进react hooks:userState、 us

2020-05-15  本文已影响0人  uuuuuuw
1111.png

React 16.8发布后带来了一项新的玩意,React发生了大改变!!!解决了很多类组件的固有缺陷,这篇文章让大家快速熟悉并掌握最常用的两个Hook:userState 和 useEffect
了解使用的过程中,还能掌握一些背后的原理,顺便实现一个豆瓣电影吧榜单应用。

准备工作

阅读之前,希望同学已经做了一些准备

  1. 掌握了react基础知识
  2. 会使用react来写代码

开始

Hook是什么?

Hook 是什么? Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。稍后我们将学习其他 Hook。

为什么需要hooks?

1.hooks出现之前, 类组件提供了完整的状态管理和声明周期控制,通常用来承载复杂的业务逻辑。
2.函数组件则是纯粹的从数据到数据映射,对状态无感知。

hooks解决了什么问题?

1.令人头痛的this绑定
2.声明周期的不合理
3.数据共享的困局(需要借助复杂的状态管理器redux)

我们先看看官方介绍 Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

userState官方示例

import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

官方讲解的比较分散,链接:https://react.docschina.org/docs/hooks-intro.html,我会从实现一个简单项目来介绍使用

等价的 class示范

 class Example extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
       count: 0
     };
   }
 
   render() {
     return (
       <div>
         <p>You clicked {this.state.count} times</p>
         <button onClick={() => this.setState({ count: this.state.count + 1 })}>
           Click me
        </button>
       </div>
     );
   }
 }

tip: 是不是觉得hooks代码清晰简单了很多

来看一个经典的计数器

function Counter() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert('You clicked on: ' + count);
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  );
}

这里问大家一个问题?

点击 Click me 按钮,count增加到 3, 点击 Show alert 按钮,在setTimeout 结束之前,继续点击 Click me 按钮,count增加到 5, 这个时候 alert 是多少?

答案:alter 是 3
你答对了么?
简单解释一下:

  1. 每次渲染相互独立,状态,时间函数都是独立的
  2. 我们再count为3的时候 触发handleAlertClick方法,这个函数记住的就是count = 3
  3. setTimeout 三秒后结束 alter 自然是3

userEffect浅析

官方文档介绍
useEffect(effectFn, deps)
使用 useEffect 我们需要忘记以前写class的时候 使用到的生命周期,函数组件跟类组件时不同的世界

  1. React会在每次渲染完后运行Effect,而依赖数组就是用来控制是否触发Effect,从而减少不必要的计算,从而优化性能
    1.1.简单说明:只要依赖数组中的每一项与上一次渲染相比没有改变,此次Effect就不会被执行
    1.2.类比:以前我们写class的时候 会使用 componentDidUpdate 来控制是否重新计算
  2. useEffect 跟class的生命周期相比,有哪些不同?
    2.1.将初次渲染(componentDidMount)、重渲染(componentDidUpdate)和销毁(componentDidUnmount)三个阶段的逻辑用一个统一的 API 去解决
    2.2.简单举例:只需要渲染一次的 我们可以直接指定 deps 为空数组 [ ]

实战小应用

1.通过Creat React App 初始化项目:npx create-react-app my-app-with-hooks
2.样式使用到了 Ant Design: yarn add antd
3.App.js源码:

import React, { useState, useEffect } from "react";

import "./App.css";
import 'antd/dist/antd.css';
import { Button, Spin, Input, Select, Table, Rate } from 'antd';

const { Option } = Select;
const columns = [
  {
    title: '电影名',
    dataIndex: 'title',
    render: (text, record) => (
      <Button type="link" onClick={() => go(record.url)}>{record.title}</Button>
    ),
  },
  {
    title: '海报',
    dataIndex: 'url',
    render: (text, record) => (
      <img src={record.cover} style={{height: 190}} alt=""/>
    ),
  },
  {
    title: '星级',
    dataIndex: 'rate',
    render: (text, record) => {
      return <Rate disabled defaultValue={Number(record.rate)/2}/>
    }
  },
];

const BASE_URL = "https://movie.douban.com/j";

function getFetchUrl(key) {
  return `${BASE_URL}/search_subjects?type=${key}&tag=热门&page_limit=50&page_start=0`
}

function go(url) {
  window.open(url)
}

function App() {
  const [countries, setCountries] = useState([]);
  const [key, setKey] = useState("movie");
  const [inputText, setInput] = useState("");

  useEffect(() => {
    const fetchCountries = async () => {
      const response = await fetch(getFetchUrl(key));
      const data = await response.json();
      setCountries(data.subjects);
    };
    fetchCountries();
  }, [key]); // deps 加入了 key监听, 所以每次key改变 都会调用这个 useEffect

  function handleChange(value) {
    setKey(value)
  }

  return (
    <div className='App'>
      <h1>本周热门排行</h1>
      <Input style={{ width: 180}} placeholder="请输入搜索的类型" value={inputText} onChange={e => setInput(e.target.value)} />
      <Button style={{marginLeft: 10, marginRight: 10}} type="primary" onClick={() => setKey(inputText)}>搜索</Button>

      <Select defaultValue="电影" style={{ width: 120 }} onChange={handleChange}>
      <Option value="tv">电视</Option>
      <Option value="moive">电影</Option>
      <Option value="radio" disabled>
        广播
      </Option>
    </Select>
    <Table dataSource={countries} columns={columns} rowKey={(record, index) => `complete${record.id}${index}`} style={{ marginTop: 20}}/>;
      {
        countries.length === 0 ? <Spin /> : <div/>
      }
    </div>
  );
}

export default App;

效果图:


image.png

相信大家看见代码应该都明白了,我这边还是简单说明一下
1.这里我创建了两个状态 key(用来筛选 tv 跟 movie)跟 countries (列表数据填充)
2.通过 useEffect 钩子进行数据获取,但是第二个参数 deps 这里依赖了 key, 只要当 key 改变,useEffect钩子就会重新触发
3.最后将数据 setCountries 赋值给 countries

注意事项

1.不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
2.只能在React函数式组件或自定义Hook中使用Hook。
3.借助eslint-plugin-react-hooks的 ESLint 插件来强制执行这两条规则,违反hooks规则,会有eslint提示
4.npm install eslint-plugin-react-hooks --save-dev
5.eslint配置

 {
   "plugins": [
     // ...
     "react-hooks"
   ],
   "rules": {
     // ...
     "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
     "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
   }
 }

useMemo简单实例理解:

父组件

import React, { useState, useEffect } from "react";

import "./App.css";
import Child from './components/Child'

const BASE_URL = "https://movie.douban.com/j";

function App() {
  const [price, setPrice] = useState('价格');
  const [content,setContent] = useState('内容');
  return (
    <div className='App'>
        <button onClick={() => setPrice(price + 1)}>price</button>
        <button onClick={() => setContent(content + 2)}>content</button>
        <Child price={price} content={content}></Child>
    </div>
  );
}
export default App;

子组件

mport React, { useState, useEffect, useMemo } from "react";

function Child({ price, content }) {
    function changePrice(price) {
      console.log('price: ', price);
      return price + '元'
    }
    // const otherPrice = useMemo(() => changePrice(price), [price]) 
    const otherPrice = changePrice(price)
    return (
        <>
          <div>{otherPrice}</div>
          <div>{content}</div>
        </>
    )
  }
  export default Child;

从上述代码可以看到,子组件的price 跟 content ,触发setPrice 或者 setContent 都会触发 changePrice方法,这意味着性能损耗,做了无用功,
当我们触发 setContent 的时候 希望不会触发 changePrice方法, 这个时候 使用 useMemo 就可以完美解决了。

上一篇下一篇

猜你喜欢

热点阅读