美文网首页
用实战带你走进react hooks:userState、 us

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

作者: uuuuuuw | 来源:发表于2020-05-15 12:02 被阅读0次
    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 就可以完美解决了。

    相关文章

      网友评论

          本文标题:用实战带你走进react hooks:userState、 us

          本文链接:https://www.haomeiwen.com/subject/kjfcohtx.html