React Native 使用来自不同导出函数的助手 api 文件中的上下文

IT技术 reactjs react-native react-hooks
2021-05-03 09:25:10

我有一个带有一些导出函数的帮助文件。我有一个带有一些共享数据的 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;

我的助手

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;

我的助手

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 文件中的函数设置上下文...然后我如何将某些函数外包到一个单独的文件中(以便更好地阅读和重用),并使用从可能异步获取地址的外包函数中获取数据。谢谢你的耐心。

2个回答

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

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

我的助手

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这使其成为一个钩子。

我的助手

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>
    );
}

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

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

获取帮助器.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;