作为 props 传递的函数不是函数

Function passed as props is not a function

我正在尝试创建一个登录模块。我有一个 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+)*(\.\w{2,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 组件。

这里的问题是 LoginController 不是 Component,如果你想让 LoginController 只是一个助手 class,你应该删除 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+)*(\.\w{2,3})+$/;
         var isValid = reg.test(this.email);


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

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

但是,如果您的目标是抽象有状态逻辑,那么您就错了。当您从 class 扩展 React.Component 时,您明确告诉 React 这个 class 是一个 Component 因此它应该 return JSX (渲染( )) 并应初始化为组件:<LoginController />,要抽象有状态逻辑,您实际上有很多非常酷的选择:

高阶组件 (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 来完成。

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

在我的例子中,LoginController 不应该是一个组件,因为在它的逻辑中没有什么可以渲染的。纯属帮手class.

现结果代码如下

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+)*(\.\w{2,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,但我相信,如果我不需要组件,我应该以 non-component 的方式尝试它,尽管它很有见地并且可能会有所帮助我在未来的复杂场景中。