【问题标题】:React Native use Context in helper api file from within different exported functionsReact Native 在不同的导出函数中使用帮助 api 文件中的上下文
【发布时间】:2021-03-19 16:52:47
【问题描述】:

我有一个包含一些导出函数的帮助文件。 我有一个包含一些共享数据的 ContextProvider。 现在我需要从辅助函数内部更改上下文中的数据。

但是,如果我在函数体内部调用 useContext 语句以及在外部调用,都会出现错误。

如果我在帮助文件中创建一个默认函数 MyHelper(),我可以在里面调用 useContext 并使其工作。 但是我不能将不同的函数用作另一个文件中的 api。只有 MyHelper()。

如何在另一个 .js 文件中调用多个导出函数并更改那里的上下文数据?

我制作了一个示例代码,它显示了我的问题,而没有原始代码的开销:

MyScreen.js

import React, {useContext} from 'react';
import {View, Button} from 'react-native';

import {MyContext} from '../MyProvider'; // assume this exists and is working
import {myChangeNameFunction} from '../helpers/MyHelper';

const MyScreen = () => { 
  const {name, setName} = useContext(MyContext); // does work here
  
  return (
    <View>
      <Button
        title={name} //just as easy example
        onPress={() => myChangeNameFunction('test')}
      />
   </View>
      
  );
}

export default MyScreen;

MyHelper.js

import React, {useContext} from 'react';
import {MyContext} from '../MyProvider';
    
    //const {name, setName} = useContext(MyContext); // does not work
    
    export const myChangeNameFunction = (test) => { 
        //const {name, setName} = useContext(MyContext); // does not work
        setName(test);
    }
    
    export const anotherFunction = (test) => { 
        //const {name, setName} = useContext(MyContext); // does not work
        setName(test);
    }

更新:

也许是一个更好的例子:

MyScreen.js

import React, {useContext} from 'react';
import {View, Text, Button} from 'react-native';
import GeocoderOsm from 'react-native-geocoder-osm';
import {MyContext} from '../MyProvider';
//import {getGPSbyAddress} from '../helpers/MyHelper'; 
// I want to exclude functions below and instead import them from helper file 

const MyScreen = () => { 
  const {address, setAddress} = useContext(MyContext);
  
  // imagine here are a lot of functions like these below.
  // I want to exclude them from this file to a MyHelper.js
  // So the MyScreen.js File is not so big and with chaos.
  // Also I could import and reuse these functions from other Screens than MyScreen.js.
  
  const getGPSbyAddress = (place) => { 
      GeocoderOsm.getGeoAddress(place).then((res) => {
      // setAddress is working here. But if I outsource the function to MyHelper.js File 
      // I can't access the setAddress in the Context. And I also can't return 
      // the res output because its kind of async - because of the then() function 
      // of the OSM lib. So I just want to save it to context if its finished.
        setAddress(res);    
      }).catch((e) => { 
        console.log('error', e)
      });
  }
  
  return (
    <View>
      <Button
        title='Get GPS'
        onPress={() => getGPSbyAddress('London') }
      />
      <Text>{address}</Text>
   </View>
      
  );
}

export default MyScreen;

MyHelper.js

import React, {useContext} from 'react';
import GeocoderOsm from 'react-native-geocoder-osm';
import {MyContext} from '../MyProvider';
    
  //const {address, setAddress} = useContext(MyContext); // does not work here
    
  export const getGPSbyAddress = (place) => { 
      //const {address, setAddress} = useContext(MyContext); // does not work here
      
      GeocoderOsm.getGeoAddress(place).then((res) => {
        setAddress(res);    // this is not working here because I cant call useContext anywhere.
      }).catch((e) => { 
        console.log('error', e)
      });
  }
  
  export const getAddressByGPS = (lat, lon) => { 
      //const {address, setAddress} = useContext(MyContext); // does not work here
      
      GeocoderOsm.getGeoCodePosition(lat, lon).then((res) => {
        setAddress(res);    // this is not working here because I cant call useContext anywhere.
      }).catch((e) => { 
        console.log('error', e)
      });
  }
  
  // ... a lot of more functions

因此,如果没有解决方案从 MyHelper.js 文件中的函数设置上下文...然后我如何将一些函数外包到一个单独的文件中(以便更好地阅读和重用)并获得我的 {address}使用从可能异步获取地址的外包函数获取的数据呈现。感谢您的耐心等待。

【问题讨论】:

  • 错误是什么?
  • 如果我在函数内部调用 useContext:“错误:无效的钩子调用。只能在函数组件的主体内部调用钩子。”如果我在它之外调用 useContext 。如果我只创建一个 MyHelper 函数并在其中使用它,错误会显示“未定义”,因为上下文挂钩超出了被调用函数的范围。
  • 添加MyHelper.js的所有代码。
  • useContext 是一个反应钩子。它需要在反应组件内部调用。但是,您可以使用内置钩子编写自己的自定义钩子(一个函数)。但是这个钩子仍然需要在组件内部调用。自定义钩子的命名约定应该以 use 开头,以便钩子 linter 插件正常工作。
  • @yudhiesh 就是这样。

标签: reactjs react-native react-hooks


【解决方案1】:

您看到错误的原因是您在组件主体之外调用了一个钩子。在您的情况下,这是 useContext 钩子。在第一种情况下,您在模块级别调用它,而在其他两种情况下,您在函数内部调用它,然后您在同样位于组件“外部”的处理程序中调用该函数。您需要做的是提取一个您在组件中无条件调用的钩子。该钩子可以公开(返回)您可以在处理程序中调用的函数。

您可以创建自己的自定义挂钩,这些挂钩由其他挂钩组成:

MyHelper.js

import {MyContext} from '../MyProvider';

export const useName = () => {
    return useContext(MyContext);
}

然后您可以在组件中使用它。请注意,自定义挂钩必须返回组件需要使用的所有内容。

MyScreen.js

import {useName} from '../helpers/MyHelper';

const MyScreen = () => { 
  const {name, setName} = useName();
  
  return (
    <View>
      <Button
        title={name}
        onPress={() => setName('test')}
      />
   </View>
      
  );
}

在这种情况下,useName 挂钩将隐式使用MyContext,而无需显式传递它。它当然可以包含更多的钩子,并根据使用它的组件的需要返回相关值。

编辑

鉴于您的更详细示例,您将在 MyHelper.js 文件中创建一个自定义挂钩,该挂钩返回所有需要的函数。它的工作方式没有任何不同,因为这些功能首先需要上下文。它们是您上下文中数据的闭包。因此,当您的助手需要首先获取上下文时,它需要调用 useContext,这使它成为一个钩子。

MyHelper.js

import {MyContext} from '../MyProvider';

export const useUserData = () => {
    const {
        name, setName,
        address, setAddress
    } = useContext(MyContext);

    const myChangeNameFunction = test => setName(test);

    const getGPSbyAddress = (place) => { 
        GeocoderOsm.getGeoAddress(place).then((res) => {
            setAddress(res);    
        }).catch((e) => { 
            console.log('error', e)
        });
     }

     const getAddressByGPS = (lat, lon) => {   
         GeocoderOsm.getGeoCodePosition(lat, lon).then((res) => {
             setAddress(res);   
         }).catch((e) => { 
             console.log('error', e)
         });
     }

     // return your data and updater functions 
     // so you can access them in your component
     return {
         name, address, 
         getGPSbyAddress, getAddressByGPS, myChangeNameFunction
     };
}

你可以像这样使用你的自定义钩子:

MyScreen.js

import {useUserData} from '../helpers/MyHelper';

const MyScreen = () => { 
    const {
        name, address, 
        getGPSbyAddress, myChangeNameFunction
    } = useUserData();

    return (
        <View>
            <Button
                title='Get GPS'
                onPress={() => getGPSbyAddress('London') }
            />
            <Text>{address}</Text>
            <Button
                title={name}
                onPress={() => myChangeNameFunction('test')}
            />
        </View>
    );
}

您当然可以在多个不同的组件中同时使用该挂钩。您还可以将此自定义挂钩拆分为多个挂钩,这些挂钩仅具有某些相关的功能。

【讨论】:

  • 谢谢,但这并不能解决我的问题。问题不是从 MyHelper 到 MyScreen 获取上下文。问题是将功能从 MyScreen 外包给 MyHelper。因为在实际示例中可能有 20 个函数。我只是希望它们在另一个文件中。到目前为止,这是有效的。但是其中一些使用 set-function 将数据设置到上下文中。从 MyScreen 我可以访问上下文。但如果我把这些功能外包出去,我就无法访问,因为到目前为止我还没有找到任何办法。
  • @MarioGotti 完全不清楚你的意思。您可以按照我的答案中提供的完全相同的方式将任意数量的功能移动到 MyHelpers。但是一旦这些函数使用了钩子,它们本身就是钩子,因此需要遵循钩子的规则。
  • @MarioGotti 也许您可以添加一个更好的示例来更好地说明您的用例?
  • @MarioGotti 你不能调用setName 而不先从上下文中获取它。此外,您的MyHelper.js 文件中不应该有直接调用setName 的函数,因为这意味着它会在渲染期间被调用。相反,辅助函数应该返回一个可以在组件中调用的函数本身。混淆最有可能在普通辅助函数和内部使用钩子的函数(也称为自定义钩子)之间。您示例中的外包助手不应调用 setName,而是公开一个在组件中调用时调用 setName 的函数。
  • 我现在已经编辑了我的应用程序并且它工作正常。再次感谢。
【解决方案2】:

另一个使用 Helper 文件和访问全局上下文的例子:

FetchHelper.js

import React, {useContext} from 'react';
import {Context} from '../ContextProvider';

export const FetchHelper = () => {
    const {globaldata, setGlobaldata} = useContext(Context); 

    const fetchDataA = async () => {  
        d = await getDataFromDB(); 
        return d; // return value
    }

    const fetchDataB = async () => {  
        d = await getDataFromDB(); 
        setGlobaldata(d); // set value in global context
        return;
    }

    return {
        fetchDataA, fetchDataB
    };
}

MyScreen.js

import React, {useEffect, useContext} from 'react';
import {Context} from '../ContextProvider';
import {FetchHelper} from '../helpers/FetchHelper';

const MyScreen = () => { 

    const {globalData, setGlobalData} = useContext(Context);    // make global context accessable

    const {fetchDataA, fetchDataB} = FetchHelper(); // make helper functions accessable 

    const fetchData = async () => {  
        
        // variation A: call helper function and await returned value. Set it in global context data here.
        
        const dataA = await fetchDataA(); 
        setGlobalData(dataA);
        
        // variation B: call helper function and global context data is set in helper function.
        
        fetchDataB();
    }

    useEffect(() => {
      fetchData();
    }, []);

    return ();
};

export default MyScreen;

【讨论】:

  • 这很有帮助。请澄清我关于自定义钩子状态的一些概念:1)使用自定义钩子的组件具有其独立的钩子状态。那么我们在钩子中使用的 useContext 状态呢,应该是全局的,不是吗?此外,如果一个组件只从自定义钩子中导入一个状态变量,如果钩子中的其他状态变量发生变化,它会重新渲染吗?当使用钩子的组件重新渲染时
猜你喜欢
  • 1970-01-01
  • 2019-04-29
  • 2021-07-18
  • 1970-01-01
  • 2020-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多