Trying to convert React.CreateClass to extends Rea

2019-09-10 09:01发布

问题:

I'm trying to convert a sample written with React.CreateClass to extends React.Component but the eventhandler doesn't get the state and fails on me. Can someone shed some light on what I'm doing wrong?

Working sample:

var Form4 = React.createClass({
    getInitialState: function () {
        return {
            username: '',
            password: ''
        }
    },
    handleChange: function (key) {
        return function (e) {
            var state = {};
            state[key] = e.target.value;
            this.setState(state);
        }.bind(this);
    },
    resetForm: function() {
        this.setState({username:'', password: ''});
    },
    changeFunkyShizzle: function() {
        this.setState({
            username: 'Tom',
            password: 'Secret'
        });
    },
    updateToServer(e) {
        console.log('update to server....');
        console.log(this.state);
        e.preventDefault();
    },
    render: function(){
        console.log(JSON.stringify(this.state, null, 4));
        return (
          <div>
            <FormFields unamepwd={this.state} handleChange={this.handleChange} updateChanges={this.updateToServer} />

        <pre>{JSON.stringify(this.state, null, 4)}</pre>
        <button onClick={this.changeFunkyShizzle}>Change is near...</button>
        <button onClick={this.resetForm}>Reset all the things!</button>
      </div>
    );
}
});

What I tried to make from it:

class Form5 extends React.Component {
    constructor() {
        super();
        this.state = {
            username: '',
            password: ''
        };
    }
    handleChange(key) {
        return function (e) {
            var state = {};
            state[key] = e.target.value;
            this.setState(state);
        }.bind(this);
    }
    changeFunkyShizzle() {
        this.setState({
            username: 'Tom',
            password: 'Secret'
        });
    }
    render() {
        let self = this;
        console.log(JSON.stringify(this.state, null, 4));
        return (
            <div>
            <FormFields unamepwd={this.state} handleChange={self.handleChange} updateChanges={self.updateToServer} />

                <pre>{JSON.stringify(this.state, null, 4)}</pre>
                <button onClick={this.changeFunkyShizzle}>Change is near...</button>
                <button onClick={this.resetForm}>Reset all the things!</button>
              </div>)
        ;
    }
}

Formfields:

var FormFields = React.createClass({
    render: function() {
        const upwd = this.props.unamepwd;
        return(
        <form>
            Username: <input
        value={upwd.username}
        onChange={this.props.handleChange('username')} />
  <br />
            Password: <input type="password"
        value={upwd.password}
        onChange={this.props.handleChange('password')} />
  <button onClick={this.props.updateChanges}>Go!</button>
</form>
        );
    }
});

When debugging I noticed that in both cases the this in this.setState(state); is something completely different. In Form5 it only provides the state while in Form4 it's a complete React object it seems with way more on it.

Could this be due to Babel transpiling it to something else or because of the class I'm extending (React.Component). I'm taking my first steps into the React.js world and already got quite some stuff running but this is not working out for me and I don't have a clear indication why not.

The gulpfile I use to transpile:

var gulp = require('gulp');
var babel = require('gulp-babel');
var sourceMaps = require('gulp-sourcemaps');

gulp.task('transpile', function () {
    gulp.src('source/**.jsx')
        .pipe(sourceMaps.init())
        .pipe(babel({ "presets": "react" }))
        .pipe(babel({ "presets": "es2015" }))
        .pipe(sourceMaps.write('.'))
        .pipe(gulp.dest('app/'));
});

gulp.task('watchers', function () {
    gulp.watch('source/**.jsx', ['transpile']);
});

gulp.task('default', ['watchers'], function () {
    console.log('gulp default task running');
});

回答1:

With class methods, the this is not automatically bound to class (except for constructor and React lifecyle methods).

Solution 1

Bind the functions to this inside the constructor:

constructor() {
    super();
    this.state = {
        username: '',
        password: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.changeFunkyShizzle= this.changeFunkyShizzle.bind(this);
}
handleChange(key) {
    return function (e) {
        var state = {};
        state[key] = e.target.value;
        this.setState(state);
    }.bind(this);
}
changeFunkyShizzle() {
    this.setState({
        username: 'Tom',
        password: 'Secret'
    });
}

Solution 2

Use arrow functions. As this is a ES6 feature, you might need to configure Babel with the correct plugin.

handleChange = (key) => (e) => {
  var state = {};
  state[key] = e.target.value;
  this.setState(state);
}

changeFunkyShizzle = () => {
   this.setState({
       username: 'Tom',
       password: 'Secret'
   });
}

Solution 3

Use an autobinding third party library to do this for you:

  • https://github.com/cassiozen/React-autobind
  • https://github.com/andreypopp/autobind-decorator