作为道具传递的函数不是函数

Posted

技术标签:

【中文标题】作为道具传递的函数不是函数【英文标题】:Function passed as props is not a function 【发布时间】:2019-12-01 13:29:54 【问题描述】:

我正在尝试创建一个登录模块。我有一个 LoginView,它定义了视图和一个 LoginController,我在其中定义了所有用户交互。现在我正在尝试合并一个逻辑,在该逻辑中,LoginController 将更改 LoginView 的状态,例如将 isLoading 的值从 false 更改为 true,以防所有输入数据都有效

登录查看

import React,  Component, Fragment from 'react';
import LoginController from '../Controller/LoginController.js';

import 
  View,
  ScrollView,
  StatusBar,
  SafeAreaView,
  TextInput,
  TouchableOpacity,
  Text,
  StyleSheet
 from 'react-native';

const styles = StyleSheet.create(
   container: 
      paddingTop: 23
   ,
   input: 
      margin: 15,
      height: 40,
      borderColor: '#7a42f4',
      borderWidth: 1
   ,
   submitButton: 
      backgroundColor: '#7a42f4',
      padding: 10,
      margin: 15,
      height: 40,
   ,
   submitButtonText:
      color: 'white'
   
);
export default class LoginView extends Component 

   constructor()
      super()
      this.state = 
         isLoading: false
       
   

   changeLoadingState = (currentLoadingState) => 

      /* Create a loader screen and incorporate it here.
      */
      this.setState(isLoading: currentLoadingState , () => 
         console.log("This is called when this.setState has resolved");
         console.log(this.state.isLoading);
       );
   

  render() 

    const con = new LoginController(this.changeLoadingState);

     return (
        <Fragment>
        <StatusBar barStyle="dark-content" />
        <SafeAreaView>
         <View style = styles.container>
            <TextInput style = styles.input
               underlineColorandroid = "transparent"
               placeholder = "Email"
               placeholderTextColor = "#9a73ef"
               autoCapitalize = "none"
               onChangeText = con.handleEmail/>

            <TextInput style = styles.input
               underlineColorAndroid = "transparent"
               placeholder = "Password"
               placeholderTextColor = "#9a73ef"
               autoCapitalize = "none"
               onChangeText = con.handlePassword/>

            <TouchableOpacity
               style = styles.submitButton
               onPress = 
                    () => con.login()
               >
               <Text style = styles.submitButtonText> Submit </Text>
            </TouchableOpacity>
         </View>
         </SafeAreaView>
         </Fragment>
      );
        

LoginController.js

import React,  Component  from 'react';
import LoginNetworkManager from '../NetworkManager/LoginNetworkManager.js';
import Loader from '../../Utils/Loader.js';

export default class LoginController extends Component 

      constructor(props) 
         super(props);
         this.state = 
            email: null,
            password: null
         ;
         this.changeLoadingState = this.changeLoadingState.bind(this);
      

      changeLoadingState = (currentLoadingState) => 
         this.props.changeLoadingState(currentLoadingState);
      

      handleEmail = (text) => 
         this.setState(email: text);
      
      handlePassword = (text) => 
         this.setState(password: text);
      

      login = () => 

         this.changeLoadingState(this.validate());
         if (this.validate() == true) 
            // Here in we will call the API
          else 
            console.log(" It's false ");
            // Do nothing
         
      

      validate = () => 
         var reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w2,3)+$/;
         var isValid = reg.test(this.email);


         if (isValid) 
            isValid = (this.password.trim().length > 0);
         

         console.log(" Tis is Valid " + isValid);
         return isValid
      
   

点击登录按钮时的错误是

   _this.props.changeLoadingState is not a function
    handleException @ ExceptionsManager.js:86
    handleError @ setUpErrorHandling.js:23
    reportFatalError @ error-guard.js:42
    __guard @ MessageQueue.js:345
    callFunctionReturnFlushedQueue @ MessageQueue.js:105
    (anonymous) @ debuggerWorker.js:80

【问题讨论】:

道具不是这样工作的。你的LoginController 不是一个组件,或者至少它不像一个组件那样使用。 我使用组件是因为我想将参数作为构造函数注入发送。基本上这个想法是 LoginView 将调用 LoginController 来处理它的用户交互,任何与 UI 相关的更改都将在 LoginView 类中完成。这样,逻辑就分开了。但是,如果有其他逻辑可以用来实现相同的理想,请告诉我 【参考方案1】:

这里的问题是LoginController 不是Component,如果您希望LoginController 只是一个辅助类,那么您应该从中删除stateprops

export default class LoginController 

      changeLoadingState = (currentLoadingState) => 

      

      handleEmail = (text) => 

      
      handlePassword = (text) => 

      

      login = () => 

         this.changeLoadingState(this.validate());
         if (this.validate() == true) 
            // Here in we will call the API
          else 
            console.log(" It's false ");
            // Do nothing
         
      

      validate = () => 
         var reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w2,3)+$/;
         var isValid = reg.test(this.email);


         if (isValid) 
            isValid = (this.password.trim().length > 0);
         

         console.log(" Tis is Valid " + isValid);
         return isValid
      
   

但是,如果您的目标是抽象有状态逻辑,那么您做错了。当你从一个类扩展 React.Component 时,你明确告诉 React 这个类是一个 Component 因此它应该返回 JSX (render()) 并且应该初始化为一个组件:&lt;LoginController /&gt;,以抽象有状态逻辑你实际上有很多非常酷的选择:

高阶组件 (HOC)

这似乎是您的用例,因为您想将一些道具注入LoginView,因此您可以将逻辑抽象为 HOC:

import React,  Component  from 'react';
import LoginNetworkManager from '../NetworkManager/LoginNetworkManager.js';
import Loader from '../../Utils/Loader.js';

export default withLogin = (ChildComponent) => 
    return class LoginController extends Component 
        constructor(props) 
            super(props);
            this.state = 
                email: null,
                password: null
            ;
            this.changeLoadingState = this.changeLoadingState.bind(this);
        
        /*
         Your logic
        */

        render()
            return <ChildComponent ...this.state />
        
    

现在在 LoginView 中,您可以像这样导出:export default withLogin(LoginView)LoginController 的状态将在 LoginView 的道具中序列化:this.props.emailthis.props.password

当然,可以使用 HOC 完成的所有事情也可以使用 renderPropshooks 完成。

【讨论】:

在这种情况下,构造函数会报错。如何将函数作为参数从 LoginView 传递给 LoginController? 给出一个错误,因为您尝试在非组件内部使用状态和道具【参考方案2】:

您没有将函数作为道具传递给 LoginController 组件。

【讨论】:

但我写的是 const con = new LoginController(this.changeLoadingState);【参考方案3】:

感谢 Dupocas 的回复,我明白何时使用组件,何时不使用。

在我的例子中,LoginController 不应该是一个组件,因为它的逻辑中没有什么可以渲染的。它纯粹是一个辅助类。

现在结果代码如下

import React,  Component, Fragment from 'react';
import LoginController from '../Controller/LoginController.js';
import Loader from '../../Utils/Loader';

import 
  View,
  StatusBar,
  SafeAreaView,
  TextInput,
  TouchableOpacity,
  Text,
  StyleSheet
 from 'react-native';

const styles = StyleSheet.create(
   container: 
      paddingTop: 23
   ,
   input: 
      margin: 15,
      height: 40,
      borderColor: '#7a42f4',
      borderWidth: 1
   ,
   submitButton: 
      backgroundColor: '#7a42f4',
      padding: 10,
      margin: 15,
      height: 40,
   ,
   submitButtonText:
      color: 'white'
   
);
export default class LoginView extends Component 

   constructor()
      super()
      this.state = 
         isLoading: false
       
       con = new LoginController();
   

   changeLoadingState = (currentLoadingState,completionBlock) => 
      this.setState(isLoading: currentLoadingState , completionBlock);
   

  render() 
     return (
        <Fragment>
        <StatusBar barStyle="dark-content" />
        <SafeAreaView>
         <View style = styles.container>
         <Loader
          loading=this.state.isLoading />
            <TextInput style = styles.input
               underlineColorAndroid = "transparent"
               placeholder = "Email"
               placeholderTextColor = "#9a73ef"
               autoCapitalize = "none"
               onChangeText = con.handleEmail/>

            <TextInput style = styles.input
               underlineColorAndroid = "transparent"
               placeholder = "Password"
               placeholderTextColor = "#9a73ef"
               autoCapitalize = "none"
               onChangeText = con.handlePassword/>

            <TouchableOpacity
               style = styles.submitButton
               onPress = 
                    () => con.login(this.changeLoadingState)
               >
               <Text style = styles.submitButtonText> Submit </Text>
            </TouchableOpacity>
         </View>
         </SafeAreaView>
         </Fragment>
      );
        

登录控制器是

import LoginNetworkManager from '../NetworkManager/LoginNetworkManager.js';
export default class LoginController  

     email = null;
     password = null;

   changeLoadingState = (currentLoadingState,viewCallback,completionBlock) => 
      viewCallback(currentLoadingState,completionBlock);
      

      handleEmail = (text) => 
         this.email = text
      
      handlePassword = (text) => 
         this.password = text
      

      login = (viewCallback) => 

         this.changeLoadingState(this.validate(),viewCallback);
         if (this.validate() == true) 

            let params =  email : this.email, password : this.password;

            LoginNetworkManager.loginAPI(params, (response,error) => 

               this.changeLoadingState(false,viewCallback,() => 

                  if (error)

                  else

                  
                );
            );
          
      

      validate = () => 
         var reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w2,3)+$/;
         var isValid = reg.test(this.email);

         console.log(" this.email " + this.email);
         console.log(" this.password " + this.password);
         if (isValid) 
            isValid = (this.password.trim().length > 0);

            console.log(" password validation ----> " + isValid);
         


         return isValid
      
   

虽然 Dupocas 提到了 HOC 和 RenderProps 和 Hooks,但我相信,如果我不需要组件,我应该以非组件方式尝试它,尽管它很有见地并且可能会在未来复杂的情况下帮助我场景。

【讨论】:

以上是关于作为道具传递的函数不是函数的主要内容,如果未能解决你的问题,请参考以下文章

测试 React 组件方法正在调用函数传递作为道具

将回调函数作为道具传递给孩子

将函数作为道具传递给 Typescript React 功能组件

vue,发射与传递函数作为道具

映射和传递 onClick 函数作为道具后如何定位特定元素

如何将函数作为道具传递给子组件并在Vue中从那里调用它?