【问题标题】:React Native - how to use 'useState()' in another functionReact Native - 如何在另一个函数中使用'useState()'
【发布时间】:2020-05-14 09:48:11
【问题描述】:

我正在尝试从另一个函数更新状态的值。

我已经这样声明它们了:


const App () => {

const [positionAX, setPositionAX] = useState(100)
const [positionAY, setPositionAY] = useState(100)

}

App之外我还有一个功能:

const setPosition = () => {
    setPositionAX(200);
    setPositionAY(200);

}

当我打电话给setPosition() 时,我得到了错误:

找不到变量:setPositionAX

找不到变量:setPositionAY

我尝试将const [positionAX, setPositionAX] = useState(100) 移动到我的文件顶部,但随后出现错误:

无效的挂钩调用。钩子只能在函数体内调用。

如何从其他函数更新该变量?

为@t-j-crowder 编辑 1

我似乎无法让它工作,我不确定我做错了什么

这是你给我的代码的现场小吃示例:https://snack.expo.io/Cd!hwDQ0m

如果我添加警报:

alert("pressed")

进入 handleClick 函数,由于某种原因它不能与 setPositionA([200, 200]) 一起工作。

另外作为补充说明: 在我的应用示例中,有两个功能和设置状态的功能,以及显示状态的功能是分开的,这就是为什么我遇到了我在最初的堆栈溢出帖子中提出的问题。

这是我想要实现的那个例子的一个小例子:

https://snack.expo.io/6TUAkpCZM

我使用 'positionX' 和 'positionY' 作为变量来定义渲染出来的元素的顶部和左侧位置,但是,positionXpositionY 变量的实际设置是在另一个中完成的功能

我是一名初级开发人员,所以我真的要感谢你在这方面的所有帮助,如果这是超级小白,我也深表歉意。

为:Muhammad Numan 编辑 2

我尝试了你的方法,但我似乎无法让它工作。我认为对于我想要实现的目标以及想要实现的目标存在一些困惑。这可能是我的错,因为我是一个新手开发者,所以请原谅我。我不想只发布我所有的代码,因为我认为人们不赞成只转储代码并像“修复这个”一样 - 但是,对于这种情况,它可能是最好的哈哈。

这是我的代码的完整小吃:https://snack.expo.io/uk8yb5xys

我想要的是这个:

指示用户从单词“start”到单词“end”画一条线。如果绘制了线并且用户线的起点和终点与给定的路径匹配,那么他们将呈现另一条线来绘制并且“开始”和“结束”的位置将移动,以便用户知道在哪里绘制。

有注释行:

// 这里是我需要更新 START 和 END 值的地方

这就是我需要“开始”一词和“结束”一词来移动到新位置的地方。

目前,当您画一条完整的线并将手指从屏幕上移开时 - 没有任何反应。但是,当您将手指放下时,开始和结束移动。当用户抬起手指时,我需要它们移动。

感谢您抽出宝贵的时间,非常感谢。

【问题讨论】:

  • 我已经完成了 snack.expo.io/i_RJHi0_v 。我在开斋节很忙
  • 你没有回复我也没有测试我的解决方案

标签: javascript react-native scope hook


【解决方案1】:

三个选项:

A.将这些函数作为参数发送并使用curried functions:

const setPositionFactory = (setPositionAX, setPositionAY) => (pos = 200) => {
    setPositionAX(pos);
    setPositionAY(pos);
}

所以你可以在你的组件中以多种方式调用它:

直接地:
setPositionFactory(setPositionAX, setPositionAY)(); // default: 200
或保留该功能以备后用:
const setPosition = setPositionFactory(setPositionAX, setPositionAY);

// ...

setPosition(220);

B.获取这些值作为 App 的道具并更新组件内的状态。它会更惯用。

C.使用 Observablerxjs 并订阅您的组件。

在您的问题代码中添加可观察对象是:

import { positionObs } from './observables';

const App () => {
    const [positionAX, setPositionAX] = useState(100);
    const [positionAY, setPositionAY] = useState(100);

    positionObs.subscribe((pos) => {
        setPositionAX(pos);
        setPositionAY(pos);
    });
}

然后在observables

import { Subject } from 'rxjs';

export const positionObs = new Subject();

export const setPosition = () => {
    positionObs.next(200);
};

【讨论】:

    【解决方案2】:

    您从useState 获得的状态设置器特定于组件实例,在第一次调用组件函数时创建。

    你有几个选择:

    1. 您可以将setPositionAXsetPositionAY 作为参数传递给setPosition

    2. 您可以将setPosition 函数放入您的组件函数中。 (是的,它每次都会重新创建,但这是相当无害的。)

    3. 您可以创建自己的位置信息挂钩。

    #3 看起来像这样:

    const usePosition = (_x = 0, _y = 0) => {
        const [x, setX] = useState(_x);
        const [y, setY] = useState(_y);
        const {current: setPosition} = useRef(function setPosition(_x = 0, _y = 0) {
            setX(x);
            setY(y);
        });
    
        return [x, y, setPosition];
    };
    

    然后在你的组件函数中:

    const [xA, yA, setPositionA] = usePosition(0, 0);
    
    // ...
    setPositionA(200, 200);
    

    或者,如果您更喜欢位置信息的元组:

    const usePosition = ([_x = 0, _y = 0] = []) => {
    //                    ^^^^^^^^^^^^^^^ ^^^^^−− optional default value
    //                     \             \
    //                      +−−−−−−−−−−−−+−−−−−−− destructuring
        const [x, setX] = useState(_x);
        const [y, setY] = useState(_y);
        // (Ensure the setter is consistent across calls, like React does)
        const {current: setPosition} = useRef(function setPosition([_x = 0, _y = 0] = []) {
            setX(x);
            setY(y);
        });
    
        return [ [x, y], setPosition ];
    };
    

    然后在你的组件函数中:

    const [positionA, setPositionA] = usePosition([0, 0]);
    //                                            ^−−−−^−−−−− passing in a tuple
    
    // ...
    setPositionA([200, 200]);
    

    这是使用 React 的元组版本的示例:

    const { useState, useRef } = React;
    
    const usePosition = ([_x = 0, _y = 0] = []) => {
        const [x, setX] = useState(_x);
        const [y, setY] = useState(_y);
        const {current: setPosition} = useRef(function setPosition([_x = 0, _y = 0] = []) {
            setX(x);
            setY(y);
        });
    
        return [ [x, y], setPosition ];
    };
    
    const Example = () => {
        const [positionA, setPositionA] = usePosition([0, 0]);
        const handleClick = () => {
            setPositionA([200, 200]);
        };
        
        return (
            <div>
                <div>Position: {positionA.join()}</div>
                <input type="button" onClick={handleClick} value="Set 200x200" />
            </div>
        );
    };
    
    ReactDOM.render(<Example/>, document.getElementById("root"));
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

    【讨论】:

    • 您好。首先,感谢您的回复。我有点困惑(因为我是菜鸟)如何实现您的解决方案 3。我在哪里放置“挂钩”代码。您的意思好像是在另一个文件中?
    • @JamesG - 不一定是另一个文件(尽管如果你要重用它,把它放在一个带有其他常用钩子的模块中是有意义的),只是不在你的组件中。
    • @t-j-cowder 恐怕我不明白你的意思。你认为你能帮我多一点,告诉我怎么做吗?我对模块/组件感到困惑。我以为它们是一样的
    • @JamesG - 我已经为答案添加了一个完整的示例(实际上是今天早上这样做并被阻止),希望这会有所帮助。注意:“模块”和“组件”是非常不同的东西。模块只是您封装在模块中的一堆相关代码。一个模块可以而且通常确实有多个组件。
    • 这不起作用。 2件事浮现在脑海。 1)这是React而不是React Native有关系吗? - 我的示例是 Native 并且 2) 我收到错误 can't find variable positionA - 我正在尝试将位置设置为与我调用它的函数不同的函数。这有意义吗?在接下来的几个小时内我将离开比赛,但可以更新答案以获取更详细的解释。
    【解决方案3】:

    useState 允许 React 排队重新渲染该特定组件

    您可以将 APP.js 函数向下传递给 AnotherComponent 并从 AnotherComponent 调用 App.js 函数并管理组件和其他方式的状态以使用 REDUX 如果您的结构很复杂

    您可以在这里测试 Snack Demo:https://snack.expo.io/iVeYXymID

    根据编辑的问题修复:https://snack.expo.io/i_RJHi0_v

    import React, { useState, useRef } from "react";
    import { Text, View, Button, StyleSheet } from "react-native";
    
    const AnotherComponent = (props) => {
      const [positionXY, setPositionXY] = useState(props.StateValue);
    
      const handleClick = () => {
        const value = [positionXY[0]+5, positionXY[1]+5];
        setPositionXY(value);
        props.setPositionValues(value);
      };
    
      return (
        <View style={styles.anotherComponentStyle}>
          <Text>
            AnotherComponent PositionX: {positionXY[0]} PositionY: {positionXY[1]}
          </Text>
          <Button onPress={handleClick} title="Update Position"></Button>
        </View>
      );
    };
    
    const Sandbox = () => {
      const StateValue = [0, 0];
      const [positionXY, setPositionXY] = useState(StateValue);
    
      const setPositionValues = (value) => {
        setPositionXY(value);
      };
    
      return (
        <View style={styles.container}>
          <AnotherComponent setPositionValues={setPositionValues} StateValue={StateValue} />
          <Text
            style={{ positon: "absolute", top: positionXY[0], left: positionXY[1] }}
          >
            Sandbox component PositionX: {positionXY[0]} PositionY: {positionXY[1]}
          </Text>
        </View>
      );
    };
    
    export default Sandbox;
    
    const styles = StyleSheet.create({
      container: { flex: 1, justifyContent: "center", alignContent: "center" },
      anotherComponentStyle: {
        backgroundColor: "#eeeffe",
        borderBottomColor: "#000000",
        borderBottomWidth: 2,
        marginBottom: 200,
        justifyContent: "center",
        alignItems: "center",
      },
    });
    
    

    【讨论】:

    • @JamesG 我已经更新了我的答案。你能测试snack.expo.io/i_RJHi0_v吗?对不起,我在开斋节很忙
    【解决方案4】:

    由于您放置在函数组件外部的函数逻辑与其内部逻辑非常紧密地耦合在一起,因此我建议将其移到内部。

    您的文件将如下所示:

    import React, { useState, useRef } from 'react';
    import { Text, View, Button } from 'react-native';
    
    const Sandbox = () => {
      const [positionA, setPositionA] = usePosition([0, 0]);
      const [x, setX] = useState(0);
      const [y, setY] = useState(0);
    
      const usePosition = ([_x = 0, _y = 0] = []) => {
        const { current: setPosition } = useRef(function setPosition([_x, _y] = []) {
          setX(x);
          setY(y);
        });
    
        return [[x, y], setPosition];
      };
    
      const handleClick = () => {
        setPositionA([200, 200]);
      };
    
      return (
        <View>
          <Text style={{ marginTop: 40, marginLeft: 40 }}>Position: {positionA.join()}</Text>
          <Button onPress={handleClick} title="Update Position"></Button>
        </View>
      );
    };
    
    export default Sandbox
    

    【讨论】:

      【解决方案5】:

      钩子只能在组件中使用!

      答案真的很简单:把你要使用钩子的方法放在组件里面。

      const App () => {
      
          const setPosition = () => {
              setPositionAX(200);
              setPositionAY(200);
      
          }
      
          const [positionAX, setPositionAX] = useState(100)
          const [positionAY, setPositionAY] = useState(100)
      
      }
      

      你认为你真的必须在单独的函数中调用 useState 吗?

      • 您可能有设计/思考/逻辑问题。
      • 对于需要在组件外部设置的变量(例如全局状态),您最好使用 Redux 或 useContext()-hook。这将是最有效的方法。

      【讨论】:

        【解决方案6】:

        首先让我先谈谈为什么您在 expo snap 上的应用程序不起作用(因为 https://snack.expo.io/uk8yb5xys 是您原始应用程序的零食)。您的数据(currentStroke 和 currentPath)仅存储在反应之外的变量中 - 它不是反应性的。这意味着虽然是的,但您的组件在渲染时读取此数据(例如,开始和结束对象根据它确定它们的位置),在初始渲染后,更改 currentStroke 不会更改开始和结束位置,因为它不会触发重新渲染这些组件。因此数据被“正确”更改,但组件不会用新数据重新绘制自身。如果您希望数据更改反映在应用程序中,您绝对应该将其保持在状态

        在这里,无论如何,我只让你的CharacterTrace 每半秒更新一次,这不是你应该做的事情,但它是为了说明这一点:现在它会每半秒重新绘制一次并“选择up" 对您的非反应性 currentStroke 的更改:

        const CharacterTrace = () => {
        
          const [path, setPath] = useState(emptyPath);
          //
          const [_,updateState] = useState()
          React.useEffect(() => {
            setInterval(() => {
              console.log('updating state only to trigger re-render...')
              updateState(Math.random())
            }, 500)
          } , [])
          //
          ...
        

        吃点这个变化:https://snack.expo.io/wwJAtOjfv

        因此,为了解决您的问题,您应该重新设计您的应用程序以将currentStroke 存储在状态中,例如,在CharacterTrace 组件const [currentStroke, setCurrentStroke] = useState(1) 中。然后,您将setCurrentStroke 传递给底层组件以执行其逻辑并在必要时调用它

        这应该以正确的方式解决您的问题,但为了回答您关于如何在组件外部调用 setter 的原始问题:

        1. 如果要从子组件中更改它,请将 setter 作为 prop 传递

        .

        const A = () => {
          const [x, setX] = useState(0)
        
          return (
            <B setX={setX} />
          )
        }
        
        const B = ({ setX }) => {
          useEffect(() => {
            setX(123)
          }, [])
          // ...
        }
        
        1. 如果你想从父组件改变它,你实际上应该做的是lift the state up到那个父组件,因为它属于那里

        2. 如果您需要从 react 组件外部更改状态,而您几乎不需要这样做,为了回答的完整性,您可以这样做

        .

        let visibleSetXReference = null
        
        const App = () => {
          const [x, setX] = useState(0)
          visibleSetXReference = setX
          return (
            <View>
              <Text>{x}</Text>
            </View>
          )
        }
        
        
        const setXFromOutsideApp = (newX) => {
          if (!visibleSetXReference) {
            console.log("App hasn't rendered yet")
          } else {
            visibleSetXReference(newX)
          }
        }
        
        
        setXFromOutsideApp(123)
        setXFromOutsideApp(456)
        setXFromOutsideApp(789)
        

        【讨论】:

          【解决方案7】:

          我们可以将值(在我们的例子中为 200 )作为道具发送到 App 功能组件。 React.useEffect() 有助于聆听道具的变化。所以,只要我们想设置值,我们就可以。

          父.js

              class Parent extends React.Component {
          
                  constructor(props) {
                      super(props)
                      this.state = {
                          x: 0,
                          y: 0
                      }
                  }
          
                  clickHandler = () => {            
                      this.setState({
                          x : 200,
                          y : 200
                      })
                  }
          
                  render() {
                      return (
                          <div>
                              <Button onPress={this.clickHandler} title="set 200"/>
                              <App setX={this.state.x} setY={this.state.y} />
                          </div>
                      )
                  }
          
              }
          

          App.js

              const App = (props) => {
                  const [positionAX, setPositionAX] = React.useState(100)
                  const [positionAY, setPositionAY] = React.useState(100)
          
                  React.useEffect(()=>{
                      setPositionAX(props.setX)
                      setPositionAY(props.setY)
                  })
                  return <div>{positionAX}, {positionAY}</div>  //displays the position
              }
          

          【讨论】:

            猜你喜欢
            • 2021-10-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-20
            • 2019-06-10
            • 1970-01-01
            • 2021-03-17
            • 2021-06-23
            相关资源
            最近更新 更多