从服务器获取数据并将值传递给 React 中的子组件(useEffect、useFetch..)

Posted

技术标签:

【中文标题】从服务器获取数据并将值传递给 React 中的子组件(useEffect、useFetch..)【英文标题】:Get data from server and pass values ​to sub components in React (useEffect, useFetch..) 【发布时间】:2022-01-05 16:11:59 【问题描述】:

为了从服务器接收一个值并将其传递给子组件,使用了useLayoutEffect。顺便说一句,useLayoutEffect 是在屏幕全部绘制完成后执行的。 由于执行顺序,传递值很困难。

第一季度。我想在渲染之前运行 useLayoutEffect ,或者知道另一种方式。 有什么方法可以推荐吗?

第二季度。在ApiServiece.js 代码的底部,有json 作为响应的返回值。但是当我输出 json 时,会输出promise[pending]。怎么了?

我正在自学,所以我迫切需要帮助。欢迎任何cmets,所以请帮忙.????

代码结构

// AdminContainer.js
import React,  useLayoutEffect, useEffect, useState, useRef  from "react";
import  useResolvedPath  from "react-router";
import  getAllUserList  from "../service/ApiService";
import DisplayUser from "./DisplayUser";

const AdminContainer = () => 

    const [allUsers, setUsers] = useState([]);
    
    useLayoutEffect (() => 
        setUsers(getAllUserList);
        // console.log(getAllUserList()); // Promise <pending>
    , []);

    return (
        <div>
            /* JSON.stringify(allUserList[0]) */
            /* allUserList.map((user, i) => 
                return <DisplayUser inputUser=user key=i />
            ) */

            <DisplayUser inputUser=allUsers />
        </div>
    )



export default AdminContainer;

// ApiServiece.js
import  connect  from 'react-redux';
import  login  from '../modules/authentication';
import store from '../modules/store';
import  API_BASE_URL  from "./app-config";
import  resolvePath  from 'react-router';
import  Sync  from '@material-ui/icons';

const ACCESS_TOKEN = "ACCESS_TOKEN";


export function signin(userDTO) 

    console.log(store.getState());

    return call("/auth/signin", "POST", userDTO).then((response) => 
        if (response.token) 
            localStorage.setItem(ACCESS_TOKEN, response.token);
            localStorage.setItem("SequenceEmail", response.email);
            window.location.href = "/";
        
    );



export function signup(userDTO) 
    return call("/auth/signup", "POST", userDTO);


export function call(api, method, request) 

    let headers = new Headers(
        "Content-Type": "application/json",
    );

    const accessToken = localStorage.getItem("ACCESS_TOKEN");
    if (accessToken && accessToken !== null) 
        headers.append("Authorization", "Bearer " + accessToken);
    

    let options = 
        headers: headers,
        url: API_BASE_URL + api,
        method: method,
    ;

    if (request) 
        options.body = JSON.stringify(request);
    
    return fetch(options.url, options)
        .then((response) =>
            response.json().then((json) => 

                if (!response.ok)                     
                    return Promise.reject(json);
                
                return json;
            )
        )
        .catch((error) => 
            console.log(error.status);
            if (error.status === 403) 
                window.location.href = "/login";
            
            return Promise.reject(error);
        );



export function getAllUserList()  
    let result = call("/auth/getAllUerList", "GET"); 
    return result;


// DisplayUser.js
const DisplayUser = (inputUser) => 

    const userRef = useRef(inputUser);
    const [user, setUser] = useState();

    useEffect(() => 
        setUser(userRef.current.inputUser);
    , [])

    console.log('setUser isArray:' + Array.isArray(setUser(userRef.current.inputUser)));

    return (
        // <div> user.username </div>
        <div> test </div>
    )


export default DisplayUser;

CodeSandbox 执行顺序Link

【问题讨论】:

在 useLayoutEffect 中使用异步函数是没有意义的,因为 useLayoutEffect 应该在重绘之前对 DOM 进行同步突变。我看到的第二个问题是 setUsers(fn(): Promise) => 它不返回数组,而是一个 Promise。 @bigless 感谢您的解释! 【参考方案1】:

首先你要为allUsers 变量(应该是一个列表)分配一个没有意义的函数。您必须首先调用该函数(并使用 useLayoutEffect 在渲染之前调用它,但请求将异步解析,因此您必须仅在请求被解析时填充allUsers)。一种解决方案是同时显示一个微调器,例如:

// AdminContainer.js
import React,  useLayoutEffect, useEffect, useState, useRef  from "react";
import  useResolvedPath  from "react-router";
import  getAllUserList  from "../service/ApiService";
import DisplayUser from "./DisplayUser";

const AdminContainer = () => 

    const [allUsers, setUsers] = useState([]);
    const [showSpinner, setSpinnerIsShowing] = useState(false)
    
    useLayoutEffect (() => 
        setSpinnerIsShowing(true)
        getAllUserList().
        then(users => setUsers(users))
        .catch(err => /*Handle error*/)
        .finally(()=> setSpinnerIsShowing(false))
    , []);

    return (
         showSpinner  ? <SomeSpinner /> : <div>
            /* JSON.stringify(allUsers[0]) */
            /* allUsers.map((user, i) => 
                return <DisplayUser inputUser=user key=i />
            ) */

            <DisplayUser inputUser=allUsers />
        </div>
    )



export default AdminContainer;

然后在 ApiService.js 中,我将使用异步等待语法来使所有内容更具可读性。

区别在于async函数总是会返回一个promise,所以你不需要直接使用Promise类(throw会等价于reject,而return会等价于resolve)。

export async function call(api, method, request) 

  let headers = new Headers(
      "Content-Type": "application/json",
  );

  const accessToken = localStorage.getItem("ACCESS_TOKEN");
  if (accessToken && accessToken !== null) 
      headers.append("Authorization", "Bearer " + accessToken);
  

  let options = 
      headers: headers,
      url: API_BASE_URL + api,
      method: method,
  ;

  if (request) 
      options.body = JSON.stringify(request);
  
  
    const response = await fetch(options.url, options)
    const json = await response.json()
    if (!response.ok)                     
        const error = new CustomError("Request not OK :C");
        error.customData = json; 
        throw error
    
    return json;
 
    //console.log(err.status);
    //if (err.status === 403) 
    //    window.location.href = "/login"; <-- Nope: handle this when calling the function
    //

 
 

 

注意Custom Error,您可以创建它来定义扩展默认javascript Error 类的附加数据:

class CustomError extends Error 
   constructor(message, customData = )
      super(message)
      this.customData = customData
   


getUserList 函数也是一个异步函数:

您可以通过两种方式创建异步函数:

使用异步语法

export async function getAllUserList()  
    return await call("/auth/getAllUserList", "GET"); 

承诺语法:

export function getAllUserList()  
    return new Promise((reject,resolve)=>
         call("/auth/getAllUserList", "GET")
         .then(res => resolve(res))
         .catch(err => reject(err))
    )
    

这两者是等价的。

如果您需要澄清,请在 cmets 中告诉我 :)

【讨论】:

非常感谢! @Antonio Della Fortuna

以上是关于从服务器获取数据并将值传递给 React 中的子组件(useEffect、useFetch..)的主要内容,如果未能解决你的问题,请参考以下文章

在 React 的子组件中将状态作为道具传递

使用 React Hooks 获取帖子并将它们作为附加道具传递给另一个组件

如何使用从 React 中的子组件获取的数据更新父组件中的状态

如何将函数从父级传递给深层嵌套的子级并将@input 值用于Angular 8中传递的函数?

使用 Typescript(TSX 文件)将道具传递给 React 中的子组件

从数据表中获取值并将其传递给标签