componentDidMount được gọi là BEFORE ref callback


87

Vấn đề

Tôi đang thiết lập một phản ứng refbằng cách sử dụng một định nghĩa hàm nội tuyến

render = () => {
    return (
        <div className="drawer" ref={drawer => this.drawerRef = drawer}>

thì trong componentDidMounttham chiếu DOM không được đặt

componentDidMount = () => {
    // this.drawerRef is not defined

Sự hiểu biết của tôi là lệnh refgọi lại nên được chạy trong quá trình gắn kết, tuy nhiên việc thêm các console.logcâu lệnh tiết lộ componentDidMountđược gọi trước khi hàm gọi lại ref.

Các mẫu mã khác mà tôi đã xem xét, ví dụ như cuộc thảo luận này trên github cho thấy cùng một giả định, componentDidMountnên được gọi sau bất kỳ lệnh refgọi lại nào được xác định trong renderđó, thậm chí nó còn được nêu trong cuộc trò chuyện

Vì vậy, componentDidMount bị kích hoạt sau khi tất cả các lệnh gọi lại ref đã được thực thi?

Đúng.

Tôi đang sử dụng react 15.4.1

Một cái gì đó khác tôi đã thử

Để xác minh refhàm đang được gọi, tôi đã thử định nghĩa nó trên lớp như vậy

setDrawerRef = (drawer) => {
  this.drawerRef = drawer;
}

sau đó trong render

<div className="drawer" ref={this.setDrawerRef}>

Ghi nhật ký bảng điều khiển trong trường hợp này cho thấy lệnh gọi lại thực sự được gọi sau componentDidMount


6
Tôi có thể sai, nhưng khi bạn đang sử dụng hàm arrow cho các methos kết xuất, nó sẽ nắm bắt giá trị thistừ phạm vi từ vựng bên ngoài lớp của bạn. Cố gắng loại bỏ cú pháp hàm mũi tên cho các phương thức lớp của bạn và xem nó có hữu ích không.
Yoshi

3
@GProst Đó là bản chất của câu hỏi của tôi. Tôi đặt console.log trong cả hai hàm và componentDidMount đang chạy đầu tiên, thứ hai là ref callback.
quickshiftin

3
Chỉ gặp một vấn đề tương tự-- đối với chúng tôi, về cơ bản, chúng tôi đã bỏ qua nó ngay từ đầu rendervà do đó cần phải tận dụng componentDidUpdate, vì componentDidMountkhông phải là một phần của vòng đời cập nhật . Có thể không phải là vấn đề của bạn, nhưng nghĩ rằng nó có thể đáng được nâng cao như một giải pháp tiềm năng.
Alexander Nied

4
Tương tự với React 16. Tài liệu nói rõ ràng ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.nhưng điều này có vẻ không đúng :(
Ryan H.

1
1. Khai báo ref arrow là: ref = {ref => { this.drawerRef = ref }}2. refs chẵn được gọi trước componentDidMount; ref chỉ có thể được truy cập sau khi kết xuất ban đầu khi div trong trường hợp của bạn được kết xuất. Vì vậy, bạn phải có thể truy cập ref ở cấp độ tiếp theo, tức là trong componentWillReceiveProps bằng cách sử dụng this.drawerRef3. Nếu bạn cố gắng truy cập trước khi mount ban đầu, bạn sẽ chỉ nhận được một trong hai giá trị không xác định của ref.
bh4r4

Câu trả lời:


155

Câu trả lời ngắn:

React đảm bảo rằng các ref được đặt trước componentDidMounthoặc componentDidUpdatehook. Nhưng chỉ dành cho trẻ em thực sự được hiển thị .

componentDidMount() {
  // can use any refs here
}

componentDidUpdate() {
  // can use any refs here
}

render() {
  // as long as those refs were rendered!
  return <div ref={/* ... */} />;
}

Lưu ý điều này không có nghĩa là “React luôn đặt tất cả các ref trước khi các hook này chạy”.
Hãy xem xét một số ví dụ mà refs không được thiết lập.


Refs không được thiết lập cho các phần tử không được hiển thị

React sẽ chỉ gọi các lệnh gọi lại ref cho các phần tử mà bạn thực sự đã trả về từ kết xuất .

Điều này có nghĩa là nếu mã của bạn trông giống như

render() {
  if (this.state.isLoading) {
    return <h1>Loading</h1>;
  }

  return <div ref={this._setRef} />;
}

và ban đầu this.state.isLoadingtrue, bạn nên không mong đợi this._setRefđể được gọi trước khi componentDidMount.

Điều này sẽ có ý nghĩa: nếu kết xuất đầu tiên của bạn được trả lại <h1>Loading</h1>, không có cách nào để React biết rằng trong một số điều kiện khác, nó trả về một thứ khác cần được đính kèm tham chiếu. Ngoài ra còn có gì để thiết lập các ref để: các <div>yếu tố không được tạo ra bởi vì các render()phương pháp nói nó không nên được trả lại.

Vì vậy, với ví dụ này, chỉ componentDidMountsẽ cháy. Tuy nhiên, khi this.state.loadingthay đổi thànhfalse , bạn sẽ thấy this._setRefđính kèm trước, sau đó componentDidUpdatesẽ kích hoạt.


Chú ý các thành phần khác

Lưu ý rằng nếu bạn chuyển các phần tử con có tham chiếu xuống các thành phần khác, có khả năng chúng đang làm điều gì đó ngăn cản việc hiển thị (và gây ra sự cố).

Ví dụ, điều này:

<MyPanel>
  <div ref={this.setRef} />
</MyPanel>

sẽ không hoạt động nếu MyPanelkhông có props.childrentrong đầu ra của nó:

function MyPanel(props) {
  // ignore props.children
  return <h1>Oops, no refs for you today!</h1>;
}

Một lần nữa, đó không phải là lỗi: React sẽ không có gì để đặt ref thành vì phần tử DOM không được tạo .


Refs không được đặt trước các vòng đời nếu chúng được chuyển đến một ReactDOM.render()

Tương tự như phần trước, nếu bạn chuyển một con có ref cho một thành phần khác, có thể thành phần này có thể làm điều gì đó ngăn cản việc gắn ref không kịp.

Ví dụ: có thể nó không trả về con từ đó render(), và thay vào đó nó đang gọi ReactDOM.render()trong một hook vòng đời. Bạn có thể tìm thấy một ví dụ về điều này ở đây . Trong ví dụ đó, chúng tôi kết xuất:

<MyModal>
  <div ref={this.setRef} />
</MyModal>

Nhưng MyModalthực hiện một ReactDOM.render()cuộc gọi trong phương thức vòng đời của nó componentDidUpdate :

componentDidUpdate() {
  ReactDOM.render(this.props.children, this.targetEl);
}

render() {
  return null;
}

Kể từ React 16, các lệnh gọi kết xuất cấp cao nhất như vậy trong một vòng đời sẽ bị trì hoãn cho đến khi các vòng đời chạy cho toàn bộ cây . Điều này sẽ giải thích tại sao bạn không nhìn thấy các tham chiếu được đính kèm trong thời gian.

Giải pháp cho vấn đề này là sử dụng các cổng thay vì các ReactDOM.rendercuộc gọi lồng nhau :

render() {
  return ReactDOM.createPortal(this.props.children, this.targetEl);
}

Bằng cách này, chúng tôi <div>với một tham chiếu thực sự được bao gồm trong đầu ra kết xuất.

Vì vậy, nếu bạn gặp sự cố này, bạn cần xác minh rằng không có gì giữa thành phần của bạn và tham chiếu có thể trì hoãn việc hiển thị con.

Không sử dụng setStateđể lưu trữ giới thiệu

Đảm bảo rằng bạn không sử dụng setStateđể lưu trữ lệnh gọi lại ref trong ref, vì nó không đồng bộ và trước khi nó "kết thúc", componentDidMountsẽ được thực thi trước.


Vẫn là một vấn đề?

Nếu không có mẹo nào ở trên hữu ích, hãy gửi sự cố trong React và chúng tôi sẽ xem xét.


2
Tôi cũng đã chỉnh sửa câu trả lời của mình để giải thích tình huống này. Xem phần đầu tiên. Hi vọng điêu nay co ich!
Dan Abramov

Xin chào @DanAbramov, cảm ơn vì điều này! Thật không may, tôi đã không thể phát triển một trường hợp có thể tái tạo khi tôi gặp nó lần đầu tiên. Đáng buồn là tôi không còn làm việc trong dự án đó nữa và đã không thể repro kể từ đó. Tuy nhiên, câu hỏi này đã trở nên đủ phổ biến, tôi đồng ý rằng cố gắng tìm ra trường hợp có thể tái tạo là chìa khóa vì nhiều người dường như đang gặp phải vấn đề này.
quickshiftin

Tôi nghĩ rằng trong nhiều trường hợp, điều này có thể là do sự hiểu lầm. Trong React 15, điều này cũng có thể xảy ra do lỗi bị nuốt (React 16 có khả năng xử lý lỗi tốt hơn và ngăn chặn điều này). Tôi rất vui khi xem xét nhiều trường hợp hơn khi điều này xảy ra, vì vậy hãy thêm chúng vào phần nhận xét.
Dan Abramov

Giúp! Tôi không thực sự nhận thấy rằng có một trình tải trước.
Nazariy

1
Câu trả lời này thực sự đã giúp tôi. Tôi đang vật lộn với một số tham chiếu "refs" trống, và tốt, hóa ra các "phần tử" hoàn toàn không được hiển thị.
MarkSkayff

1

Một quan sát khác về vấn đề.

Tôi nhận ra rằng sự cố chỉ xảy ra khi ở chế độ phát triển. Sau khi điều tra thêm, tôi thấy rằng việc vô hiệu hóa react-hot-loadertrong cấu hình Webpack của tôi sẽ ngăn chặn sự cố này.

tôi đang dùng

  • "react-hot-loader": "3.1.3"
  • "webpack": "4.10.2",

Và đó là một ứng dụng điện tử.

Cấu hình phát triển Webpack một phần của tôi

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')

module.exports = merge(baseConfig, {

  entry: [
    // REMOVED THIS -> 'react-hot-loader/patch',
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    '@babel/polyfill',
    './app/index'
  ],
  ...
})

Nó trở nên đáng ngờ khi tôi thấy rằng việc sử dụng hàm nội tuyến trong render () đang hoạt động, nhưng sử dụng phương thức ràng buộc thì bị lỗi.

Hoạt động trong mọi trường hợp

class MyComponent {
  render () {
    return (
      <input ref={(el) => {this.inputField = el}}/>
    )
  }
}

Sự cố với trình tải phản ứng nóng (ref không được xác định trong componentDidMount)

class MyComponent {
  constructor (props) {
    super(props)
    this.inputRef = this.inputRef.bind(this)
  }

  inputRef (input) {
    this.inputField = input
  }

  render () {
    return (
      <input ref={this.inputRef}/>
    )
  }
}

Thành thật mà nói, tải lại nóng thường có vấn đề để có được "đúng". Với các công cụ dành cho nhà phát triển cập nhật nhanh chóng, mỗi dự án có một cấu hình khác nhau. Có thể cấu hình cụ thể của tôi có thể được sửa. Tôi sẽ cho bạn biết ở đây nếu đó là trường hợp.


Điều này có thể giải thích tại sao tôi gặp sự cố với điều này trong CodePen, nhưng việc sử dụng hàm nội tuyến không hữu ích trong trường hợp của tôi.
robartsd

0

Vấn đề cũng có thể phát sinh khi bạn cố gắng sử dụng một ref của một thành phần chưa được gắn kết như sử dụng một ref trong setinterval và không xóa khoảng thời gian đã đặt trong khi ngắt kết nối thành phần.

componentDidMount(){
    interval_holder = setInterval(() => {
    this.myref = "something";//accessing ref of a component
    }, 2000);
  }

luôn luôn rõ ràng như ví dụ,

componentWillUnmount(){
    clearInterval(interval_holder)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.