Bereaksi: "ini" tidak ditentukan di dalam fungsi komponen

152
class PlayerControls extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      loopActive: false,
      shuffleActive: false,
    }
  }

  render() {
    var shuffleClassName = this.state.toggleActive ? "player-control-icon active" : "player-control-icon"

    return (
      <div className="player-controls">
        <FontAwesome
          className="player-control-icon"
          name='refresh'
          onClick={this.onToggleLoop}
          spin={this.state.loopActive}
        />
        <FontAwesome
          className={shuffleClassName}
          name='random'
          onClick={this.onToggleShuffle}
        />
      </div>
    );
  }

  onToggleLoop(event) {
    // "this is undefined??" <--- here
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
  }

Saya ingin memperbarui loopActivestatus pada toggle, tetapi thisobjek tidak ditentukan dalam handler. Menurut dokumen tutorial, saya thisharus merujuk pada komponen. Apakah saya melewatkan sesuatu?

Maximus S
sumber

Jawaban:

211

ES6 React.Component tidak secara otomatis mengikat metode. Anda harus mengikatnya sendiri di konstruktor. Seperti ini:

constructor (props){
  super(props);

  this.state = {
      loopActive: false,
      shuffleActive: false,
    };

  this.onToggleLoop = this.onToggleLoop.bind(this);

}
Ivan
sumber
23
jika Anda mengubah properti onClick Anda () => this.onToggleLoopsetelah memindahkan fungsi onToggleLoop ke kelas reaksi Anda, itu akan berfungsi juga.
Sam
71
Apakah Anda benar-benar harus mengikat setiap metode setiap kelas reaksi? Bukankah itu sedikit gila?
Alex L
6
@AlexL Ada cara untuk melakukannya tanpa mengikat metode secara eksplisit. jika Anda menggunakan babel, dimungkinkan untuk mendeklarasikan setiap metode pada komponen Bereaksi sebagai fungsi panah. Ada beberapa contoh di sini: babeljs.io/blog/2015/06/07/react-on-es6-plus
Ivan
7
Tapi mengapa tidak thisterdefinisi sejak awal? Saya tahu thisdalam Javascript tergantung pada bagaimana fungsinya dipanggil, tetapi apa yang terjadi di sini?
Maverick
1
tetapi bagaimana saya bisa melakukan ini karena fungsinya tidak didefinisikan sampai setelah konstruktor? Saya mendapatkan "Tidak dapat membaca properti 'bind' yang tidak terdefinisi" jika saya mencoba melakukan ini di konstruktor bahkan jika fungsi saya didefinisikan di kelas :(
rex
87

Ada beberapa cara.

Salah satunya adalah menambahkan this.onToggleLoop = this.onToggleLoop.bind(this); konstruktor.

Lainnya adalah fungsi panah onToggleLoop = (event) => {...} .

Lalu ada onClick={this.onToggleLoop.bind(this)}.

J. Mark Stevens
sumber
1
Mengapa onToogleLoop = () => {} berfungsi? Saya mendapat masalah yang sama dan saya bindet di konstruktor saya tetapi tidak berhasil ... dan sekarang saya telah melihat posting Anda dan mengganti metode saya dengan sintaks fungsi panah dan berfungsi. Bisakah Anda menjelaskannya kepada saya?
Guchelkaben
5
dari developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ; Fungsi panah tidak membuat ini sendiri, nilai ini dari konteks eksekusi terlampir digunakan.
J. Mark Stevens
1
Perhatikan bahwa mengikat inline di onClickakan mengembalikan fungsi baru setiap render dan dengan demikian sepertinya nilai baru telah diteruskan untuk prop, mengacaukan shouldComponentUpdatedalam PureComponents.
Ninjakannon
24

Tulis fungsi Anda dengan cara ini:

onToggleLoop = (event) => {
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
}

Fungsi Panah Gemuk

pengikatan untuk kata kunci ini adalah sama di luar dan di dalam fungsi panah gemuk. Ini berbeda dari fungsi yang dideklarasikan dengan fungsi, yang dapat mengikat ini ke objek lain pada saat doa. Mempertahankan pengikatan ini sangat mudah untuk operasi seperti pemetaan: this.items.map (x => this.doSomethingWith (x)).

ShaTin
sumber
Jika saya melakukan itu, saya dapat ReferenceError: fields are not currently supported.
Pavel Komarov
Ini berfungsi jika di dalam konstruktor saya mengatakan this.func = () => {...}, tetapi saya menganggap itu sebagai semacam konyol dan ingin menghindarinya jika memungkinkan.
Pavel Komarov
Sangat buruk sehingga Anda tidak dapat menggunakan sintaks kelas normal di Bereaksi!
Kokodoko
11

Saya mengalami ikatan serupa dalam fungsi render dan akhirnya melewati konteks thisdengan cara berikut:

{someList.map(function(listItem) {
  // your code
}, this)}

Saya juga telah menggunakan:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}
duhaime
sumber
Itu banyak fungsi yang tidak perlu yang Anda buat di sana, masing-masing dan setiap kali daftar diberikan ...
TJ Crowder
@ TJCrowder Ya benar fungsi-fungsi ini dibuat lagi setiap kali render dipanggil. Lebih baik membuat fungsi sebagai metode kelas dan mengikatnya sekali ke kelas, tetapi bagi pemula pengikatan konteks manual mungkin membantu
duhaime
2

Anda harus memperhatikan bahwa itu thistergantung pada bagaimana fungsi dipanggil yaitu: ketika suatu fungsi dipanggil sebagai metode suatu objek, thisitu diatur ke objek yang dipanggil oleh metode tersebut.

this dapat diakses dalam konteks JSX sebagai objek komponen Anda, sehingga Anda dapat memanggil metode yang Anda inginkan sebaris this metode.

Jika Anda hanya meneruskan referensi ke fungsi / metode, tampaknya reaksi akan memanggilnya sebagai fungsi independen.

onClick={this.onToggleLoop} // Here you just passing reference, React will invoke it as independent function and this will be undefined

onClick={()=>this.onToggleLoop()} // Here you invoking your desired function as method of this, and this in that function will be set to object from that function is called ie: your component object
Jakub Kutrzeba
sumber
1
Benar, Anda bahkan dapat menggunakan baris pertama yaitu dengan onClick={this.onToggleLoop}ketentuan bahwa di kelas komponen Anda, Anda telah mendefinisikan bidang (properti)onToggleLoop = () => /*body using 'this'*/
gvlax
1

Jika Anda menggunakan babel, Anda mengikat 'ini' menggunakan operator mengikat ES7 https://babeljs.io/docs/en/babel-plugin-transform-function-bind#auto-self-binding

export default class SignupPage extends React.Component {
  constructor(props) {
    super(props);
  }

  handleSubmit(e) {
    e.preventDefault(); 

    const data = { 
      email: this.refs.email.value,
    } 
  }

  render() {

    const {errors} = this.props;

    return (
      <div className="view-container registrations new">
        <main>
          <form id="sign_up_form" onSubmit={::this.handleSubmit}>
            <div className="field">
              <input ref="email" id="user_email" type="email" placeholder="Email"  />
            </div>
            <div className="field">
              <input ref="password" id="user_password" type="new-password" placeholder="Password"  />
            </div>
            <button type="submit">Sign up</button>
          </form>
        </main>
      </div>
    )
  }

}
Henry Jacob
sumber
0

Jika Anda memanggil metode yang Anda buat dalam metode siklus hidup seperti componentDidMount ... maka Anda hanya dapat menggunakan this.onToggleLoop = this.onToogleLoop.bind(this)dan fungsi panah gemukonToggleLoop = (event) => {...} .

Pendekatan normal deklarasi fungsi dalam konstruktor tidak akan bekerja karena metode siklus hidup disebut sebelumnya.

Guchelkaben
sumber
0

dalam kasus saya ini adalah solusinya = () => {}

methodName = (params) => {
//your code here with this.something
}
Alex
sumber
0

Dalam kasus saya, untuk komponen tanpa kewarganegaraan yang menerima referensi dengan forwardRef, saya harus melakukan apa yang dikatakan di sini https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

Dari ini (onClick tidak memiliki akses yang setara dengan 'ini')

const Com = forwardRef((props, ref) => {
  return <input ref={ref} onClick={() => {console.log(ref.current} } />
})

Untuk ini (berfungsi)

const useCombinedRefs = (...refs) => {
  const targetRef = React.useRef()

  useEffect(() => {
    refs.forEach(ref => {
      if (!ref) return

      if (typeof ref === 'function') ref(targetRef.current)
      else ref.current = targetRef.current
    })
  }, [refs])

  return targetRef
}

const Com = forwardRef((props, ref) => {
  const innerRef = useRef()
  const combinedRef = useCombinedRefs(ref, innerRef)

  return <input ref={combinedRef } onClick={() => {console.log(combinedRef .current} } />
})
GWorking
sumber