React Native-如何在另一个函数中使用“ useState()”

时间:2020-05-14 09:48:11

标签: javascript react-native scope hook

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

我已经这样声明了他们:


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])一起使用。

另请注意: 在我的应用示例中,有两个功能以及设置状态的功能和显示状态的功能是分开的,这就是为什么我要在最初的Stack溢出帖子中提出要解决的问题。

以下是我要达到的目标的一个小例子:

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

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

我是一名初级开发人员,所以我真的要感谢您对此提供的所有帮助,如果这是超级nooby,也深表歉意。

编辑2:穆罕默德·努曼

我尝试了您的方法,但似乎无法正常工作。我想对我想要实现的目标以及在哪里实现实现感到困惑。因为我是noobie开发人员,所以这可能是我的错,请原谅我。我不想只发布我的所有代码,因为我认为人们对此不屑一顾,以至于人们只能转储代码并喜欢“修复此问题”-但是,在这种情况下,这可能是最好的哈哈。

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

这是我想要的:

指示用户在单词“开始”到单词“结束”之间划一条线。如果绘制了线并且用户线的起点和终点与给定的路径匹配,则会向他们显示另一条线进行绘制,并且“开始”和“结束”的位置将移动,以便用户知道在哪里绘制。

有一条评论行:

///在这里我需要更新开始和结束的值

这是我需要“开始”和“结束”两个词移到新位置的地方。

目前,当您绘制一条完整的线并从屏幕上移开手指时,什么也没有发生。但是,当您将手指放在起点和终点时,请移动。当用户抬起手指时,我需要它们移动。

感谢您的宝贵时间,谢谢。

7 个答案:

答案 0 :(得分:4)

三个选项:

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

答案 1 :(得分:3)

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>

答案 2 :(得分:0)

由于放置在功能组件外部的功能逻辑与内部的逻辑紧密结合,因此建议将其移入内部。

您的文件如下所示:

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

答案 3 :(得分:0)

useState允许React对该特定组件的重新渲染进行排队

您可以将APP.js函数向下传递到AnotherComponent,然后从AnotherComponent调用App.js函数,并在两个组件上管理状态,并在结构复杂时使用其他方式使用 REDUX

您可以在此处测试小吃演示: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",
  },
});

enter image description here

答案 4 :(得分:0)

只能在组件中使用挂钩!

答案很简单:将要使用钩子的方法放在组件内部。

const App () => {

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

    }

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

}

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

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

答案 5 :(得分:0)

首先让我开始解释为何您在博览会零食上的应用无法正常工作(鉴于此https://snack.expo.io/uk8yb5xys是您原始应用的零食)。您的数据(currentStroke和currentPath)仅存储在react之外的变量中-它不是被动的。这意味着虽然是,但您的组件在渲染时会读取此数据(例如,Start和End对象根据该数据确定其位置),在初始渲染之后,更改currentStroke不会更改Start和End的位置,因为它不会触发重新渲染这些组件。因此,数据可以“正确”更改,但是组件不会使用新数据重绘自身。如果您希望数据更改反映在应用程序中,则绝对应该保持状态

在这里,无论如何我都只会使您的CharacterTrace每隔半秒更新一次,这不是您应该做的事情,但这是为了说明这一点:现在它将每隔半秒重绘一次,然后对非反应性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作为道具传递

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)

答案 6 :(得分:0)

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

Parent.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
    }