zoukankan      html  css  js  c++  java
  • 从零开始的野路子React/Node(5)近期Hooks使用体会

    上周实习生大佬休假,导致疯狂赶工,在一如既往的复制-黏贴-修改中,竟也渐渐琢磨出一点前端的感觉来。这一期主要讲讲最近使用Hooks的心得。

    (以下梗皆出自B站最近挺火的《啊!海军》)

    1、useState 监听自身的改变

    useState可以视作专门监听某一个变量的改变,当其发生变化时,重新渲染页面。

    useState监听的这个变量不仅仅可以是简单类型(比如整数,字符串……)也可以是一个Object。这也就意味着其实useState可以同时监听多个值,比如(新建一个UpAndDown组件,再把它放入App.js中):

    import React, {useState} from 'react';
    
    export default function UpAndDown() {
        const [comment, setComment] = useState({"up":0, "down":0});
    
        const handleUp = () => {
            setComment({...comment, up:comment.up+1})
        };
    
        const handleDown = () => {
            setComment({...comment, down:comment.down+1})
        };
    
        return (
            <>
                <p>{ comment.up }</p>
                <button onClick={ handleUp }>+很有精神</button>
                <p>{ comment.down }</p>
                <button onClick={ handleDown }>-听不见</button>
            </>
        )
    };

    我们利用了同一个useState来分别监听up和down两个值得变化,任意一个发生改变时(点击“+很有精神”或者“-听不见”),都会触发重渲染来更新页面(两者分开计数,互不影响)。

    2、useEffect 加载与被动改变

    useEffect只在两种时候执行内部的内容,从而触发重渲染。一是组件加载的时候,另一个是[]参数中的内容更新的时候(被动更新)。

    比如以下这段(新建一个AddOn组件,再把它放入App.js中):

    import React, {useState, useEffect} from 'react';
    
    export default function AddOn() {
        const [result, setResult] = useState(0);
        const [temp, setTemp] = useState(10);
        var oops = 20;
    
        const handleClick = () => {
            console.log("按了一下")
            setResult(result + 1)
        };
    
        useEffect(() => {
            console.log("刷新了")
            setTemp(temp + result)
        }, [result]);
    
        console.log(result);
        console.log(temp);
    
        return (
            <>
                <p>{ result }</p>
                <button onClick={handleClick}>+1</button>
            </>
        )
    }

    在组件刚加载的时候,我们可以看到useEffect中的内容执行了一次:

    接着我们点击按钮,每次点击都触发了result的改变,由于result在useEffect的[]参数中,所以useEffect中的内容会被执行:

    现在我们把[]中的内容换成另一个不会因为点击而改变的变量oops:

    import React, {useState, useEffect} from 'react';
    
    export default function AddOn() {
        const [result, setResult] = useState(0);
        const [temp, setTemp] = useState(10);
        var oops = 20;
    
        const handleClick = () => {
            console.log("按了一下")
            setResult(result + 1)
        };
    
        useEffect(() => {
            console.log("刷新了")
            setTemp(temp + result)
        }, [oops]);
    
        console.log(result);
        console.log(temp);
    
        return (
            <>
                <p>{ result }</p>
                <button onClick={handleClick}>+1</button>
            </>
        )
    }

    我们会发现,除了刚加载时候的一次执行之外,由于oops没有变化过,所以useEffect中的内容就一直不执行了(temp一直是10):

    那么根据这一点,如果我们直接用一个空的[],我们就可以令useEffect只执行一次,即在组件刚加载时运行一次,之后就再也不运行了。比如我们想从后端一次性地取一批数据过来(在之后的交互中不再取数据),就可以用这种方法。

    当然,useEffect的[]中也可以加入多个值,只要任意一个更新了,那么useEffect中的内容就会被执行一次:

    import React, {useState, useEffect} from 'react';
    
    export default function AddOn() {
        const [result, setResult] = useState(0);
        const [something, setSomething] = useState(0);
        const [temp, setTemp] = useState(10);
    
        const handleClick = () => {
            console.log("按了一下")
            setResult(result + 1)
        };
    
        const handleSomethingClick = () => {
            console.log("按了个寂寞")
            setSomething(something + 1)
        };
    
        useEffect(() => {
            console.log("刷新了")
            setTemp(temp + result + something)
        }, [result, something]);
    
        console.log(result);
        console.log(something);
        console.log(temp);
    
        return (
            <>
                <p>{ result }</p>
                <button onClick={handleClick}>+result</button>
                <p>{ something }</p>
                <button onClick={handleSomethingClick}>+something</button>
            </>
        )
    }

    这里我们有两个按钮,点击后分别更新result和something,从console中我们可以看到,无论我们点击哪个按钮,最后都会更新temp的值,也就是说useEffect都会被触发。

    useEffect的另一个主要作用,就是帮助渲染从后端拉取的数据。比如我有个很简单的后端:

    var express = require('express');
    var cors = require('cors');
    
    var app = express();
    
    var corsOptions = {
      credentials:true,
      origin:'http://localhost:3000',
      optionsSuccessStatus:200
    };
    app.use(cors(corsOptions));
    
    app.use(express.urlencoded({extended: true})); // 必须要加
    app.use(express.json()); // 必须要加
    
    app.post('/', function (req, res) {
      let p = req.body.name
      res.send(`${p}很有精神`)
    });
    
    app.listen(5000, function() {
      console.log('App listening on port 5000...')
    });

    它有一个POST方法,即前端传入一个名字XXX,后端返回“XXX很有精神”。

    我参考了一下实习生大佬的写法,他一般会在前端写一个Service文件,负责对接后端的API们:

    import axios from 'axios';
    
    const api = "http://localhost:5000";
    
    class BackendService {
        postInfo(body) {
            return new Promise((resolve) => resolve(axios.post(`${api}`, body)));
        }
    }
    
    export default new BackendService();

    然后再用一个自定义的hook负责加载和拉取(自定义的hook貌似都必须写成useXxx):

    import BackendService from "./BackendService";
    import { useState, useEffect } from 'react';
    
    export default function useBackend(name) {
        const [info, setInfo] = useState(null);
        const [error, setError] = useState(null);
    
        console.log(info);
    
        useEffect(() => {
            BackendService.postInfo({"name":name})
            .then(response => {
                setInfo(response.data)
            })
            .catch(error => {
                setError("后端错误")
            })
        }, [name]);
    
        return [info, error];
    }

    从后端成功拉取response之后返回相应的内容,这里useEffect的[]中是name,也就是只要name变了,那就触发useEffect内部的内容。

    我们再写一个组件来看看(新建一个BattleShip组件,再把它放入App.js中):

    import React, { useState } from 'react';
    import useBackend from "./useBackend";
    
    export default function Battleship() {
        const [name, setName] = useState("森下下士");
        const info = useBackend(name);
    
        console.log(name);
        console.log(info);
    
        return (
            <div>
                <p>{ info }</p>
                <button onClick={ () => setName("天尊杨戬") }>天尊杨戬</button>
                <button onClick={ () => setName("天山新泰罗") }>天山新泰罗</button>
                <button onClick={ () => setName("挺甜一郎") }>挺甜一郎</button>
            </div>
        );
    }

    我们从后端拉取的内容会通过useBackend这个自定义hook拉入info中,我们可以看一下结果:

    由于请求是异步的,所以一开始会先返回null(似乎可以理解成位置我先占了,事情我一会儿再做),后端的response来了之后再重渲染了页面。有时候可能会由于这个占位的null产生一些错误,一般加个条件判断就能解决。

    3、useContext 跨组件

    在我认识useContext之前,跨组件的获取/修改状态往往是个很蛋疼的问题,通过状态提升和props转来转去有时候把自己都绕晕了,而useContext则提供了一种相对简单的方法。

    首先,我们来写一个StudentContext.js:

    import React, { useState } from 'react';
    
    const StudentContext = React.createContext();
    
    function StudentContextProvider(props) {
        const [currentStudent, setCurrentStudent] = useState(null);
    
        return (
            <StudentContext.Provider value={{currentStudent:currentStudent, setCurrentStudent:setCurrentStudent}}>
                {props.children}
            </StudentContext.Provider>
        );
    }
    
    function StudentContextConsumer(props) {
        return (
            <StudentContext.Consumer>
                {props.children}
            </StudentContext.Consumer>
        );
    }
    
    export { StudentContextProvider, StudentContextConsumer, StudentContext };

    我们可以把Context看做是个中转站,我们所需要的状态都被储存在Context里,而其他组件都连接至这个中转站,从而查询或者修改其中的状态。Context内部的本质其实也就是一个或者一堆useState。

    在这里,我们导出的StudentContext是个Object,包含了2个内容,一个是变量currentStudent,另一个是设置currentStudent用的函数setCurrentStudent。

    接下来,我们新建2个组件,一个负责查询Context中的状态:

    import React, { useContext } from 'react';
    import { StudentContext } from "./StudentContext";
    
    export default function StudentCard() {
        var studentCxt = useContext(StudentContext);
        console.log(studentCxt);
    
        return (
            <div>{ studentCxt.currentStudent }</div>
        );
    };

    一个负责修改Context中的状态:

    import React, { useContext } from 'react';
    import { StudentContext } from "./StudentContext";
    
    export default function CallStudent() {
        var studentCxt = useContext(StudentContext);
    
        return (
            <>
                <button onClick={ () => studentCxt.setCurrentStudent("天尊杨戬") }>福冈县</button>
                <button onClick={ () => studentCxt.setCurrentStudent("天山新泰罗") }>东京府</button>
                <button onClick={ () => studentCxt.setCurrentStudent("挺甜一郎") }>岩手县</button>
            </>
        )
    };

    最后,我们将这两个组件都放入一个父组件中,父组件被Context的Provider包起来,意味着内部的所有子组件都可以共享Context这个中转站。

    import React, { useContext } from 'react';
    import { StudentContextProvider } from "./StudentContext";
    import StudentCard from "./StudentCard";
    import CallStudent from "./CallStudent";
    
    export default function Students(props) {
        return (
            <StudentContextProvider>
                <StudentCard/>
                <CallStudent/>
            </StudentContextProvider>
        );
    };

    看一下效果:

    每次点击CallStudent组件中的按钮,都会更新Context中的状态,而这个状态会被传达到StudentCard这个组件中,并显示出来。这也就完成了跨组件的状态传递。

    以上也就是近期的一些心得啦,真心觉得全栈还是很伟大的,有时候光前端实现某个功能就令人痛不欲生了,还要保证跟后端的同步连接和协调,真是太不容易了。路还很长,继续修炼~

    代码见:

    https://github.com/SilenceGTX/play_with_hooks

  • 相关阅读:
    laravel实现第三方登录(qq登录)
    laravel实现发送qq邮件
    第一个微信小程序(实现点击一个按钮弹出toast)
    Android笔记: 实现手机震动效果
    Android笔记: ListView基本用法-ArrayAdapter
    自适应网页设计
    javaWeb中,文件上传和下载
    jquery attr()方法
    jsp中的JSTL与EL表达式用法
    html中的事件属性
  • 原文地址:https://www.cnblogs.com/silence-gtx/p/13701864.html
Copyright © 2011-2022 走看看