変更をリッスンしたい外部の(コンポーネントの)監視可能なオブジェクトがあります。オブジェクトが更新されると、変更イベントが発行され、変更が検出されたときにコンポーネントを再レンダリングしたいと思います。

トップレベルではこれが可能でしたが、コンポーネント内では機能しません(メソッドはオブジェクトを返すだけReact.renderなので、これは理にかなっています)。render

コード例は次のとおりです。

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}

ボタンをクリックすると内部的this.render()にが呼び出されますが、実際にはレンダリングが発生しません(によって作成されたテキスト{Math.random()}は変更されないため、これが実際に動作していることがわかります)。this.setState()ただし、の代わりに単に呼び出す場合はthis.render()、正常に機能します。

だから私は私の質問だと思います: Reactコンポーネントは再レンダリングするために状態を持っている必要がありますか?状態を変更せずにコンポーネントをオンデマンドで強制的に更新する方法はありますか?

応答

クラスコンポーネントでは、呼び出しthis.forceUpdate()て強制的に再レン​​ダリングすることができます。

ドキュメント:https ://facebook.github.io/react/docs/component-api.html

関数コンポーネントには、に相当するものはありませんが、フックを使用して強制的に更新する方法を考案forceUpdateできますuseState

forceUpdateReactの考え方から逸脱しているため、避ける必要があります。forceUpdateReactのドキュメントは、いつ使用される可能性があるかの例を引用しています。

By default, when your component's state or props change, your component will re-render. However, if these change implicitly (eg: data deep within an object changes without changing the object itself) or if your render() method depends on some other data, you can tell React that it needs to re-run render() by calling forceUpdate().

forceUpdateただし、オブジェクトが深くネストされていても不要であるという考えを提案したいと思います。不変のデータソースを使用することにより、変更の追跡が安価になります。変更すると常に新しいオブジェクトが生成されるため、オブジェクトへの参照が変更されたかどうかを確認するだけで済みます。ライブラリImmutableJSを使用して、不変のデータオブジェクトをアプリに実装できます。

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your component "pure" and your application much simpler and more efficient.forceUpdate()

再レンダリングする要素のキーを変更すると機能します。状態を介して要素にキープロップを設定し、設定状態を更新して新しいキーを設定する場合。

<Element key={this.state.key} /> 

次に、変更が発生し、キーをリセットします

this.setState({ key: Math.random() });

これにより、キーが変更されている要素が置き換えられることに注意してください。これが役立つ場合の例は、画像のアップロード後にリセットしたいファイル入力フィールドがある場合です。

OPの質問に対する真の答えは、forceUpdate()このソリューションがさまざまな状況で役立つことを発見したことです。また、自分で使用していることに気付いた場合はforceUpdate、コードを確認して、別の方法があるかどうかを確認することをお勧めします。

注1-9-2019:

上記(キーの変更)は、要素を完全に置き換えます。変更を加えるためにキーを更新していることに気付いた場合は、コードのどこかに問題がある可能性があります。inキーを使用Math.random()すると、レンダリングごとに要素が再作成されます。このようにキーを更新することはお勧めしません。reactはキーを使用して、物事を再レンダリングするための最良の方法を決定するからです。

実際には、追加のロジックがに実装されている場合、または単にを返す場合に再レンダリングをトリガーしない可能性があるため、これforceUpdate()が唯一の正しい解決策です。setState()shouldComponentUpdate()false

アップデートさせる()

Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). more...

setState()

setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). more...


forceUpdate()コンポーネント内から呼び出すことができますthis.forceUpdate()


フック:Reactのフックを使用してコンポーネントを強制的に再レン​​ダリングするにはどうすればよいですか?


ところで:状態を変更しているのですか、それともネストされたプロパティが伝播しないのですか?

2021年と2022年では、これはReact機能コンポーネントをforceUpdateする公式の方法です。

const [, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    forceUpdate();
  }

OPはクラスコンポーネント用です。しかし、2015年に質問があり、フックが利用できるようになったため、多くの人がforceUpdate機能コンポーネントを検索する可能性があります。この少しは彼らのためです。

私はforceUpdate次のことをすることによって避けました

WRONG WAY : do not use index as key

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)

CORRECT WAY : Use data id as key, it can be some guid etc

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)

したがって、このようなコードの改善を行うことで、コンポーネントは一意になり、自然にレンダリングされます

関係(親子)に拘束されない2つのReactコンポーネントを通信させたい場合は、Fluxまたは同様のアーキテクチャを使用することをお勧めします。

実行したいのは、モデルとそのインターフェイスを保持する監視可能なコンポーネントstateストアの変更をリッスンし、レンダリングを変更するデータをのように保存することですMyComponentストアが新しいデータをプッシュすると、コンポーネントの状態が変更され、レンダリングが自動的にトリガーされます。

通常は、の使用を避けるようにしてくださいforceUpdate()ドキュメントから:

Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render(). This makes your application much simpler and more efficient

フックを使用するか、HOCを選択してください

フックまたはHOC(高次コンポーネント)パターンを使用すると、店舗が変更されたときに自動更新を行うことができます。これは、フレームワークのない非常に軽量なアプローチです。

useStoreHooksストアの更新を処理する方法

interface ISimpleStore {
  on: (ev: string, fn: () => void) => void;
  off: (ev: string, fn: () => void) => void;
}

export default function useStore<T extends ISimpleStore>(store: T) {
  const [storeState, setStoreState] = useState({store});
  useEffect(() => {
    const onChange = () => {
      setStoreState({store});
    }
    store.on('change', onChange);
    return () => {
      store.off('change', onChange);
    }
  }, []);
  return storeState.store;
}

withStoresHOCはストアの更新を処理します

export default function (...stores: SimpleStore[]) {
  return function (WrappedComponent: React.ComponentType<any>) {
    return class WithStore extends PureComponent<{}, {lastUpdated: number}> {
      constructor(props: React.ComponentProps<any>) {
        super(props);
        this.state = {
          lastUpdated: Date.now(),
        };
        this.stores = stores;
      }

      private stores?: SimpleStore[];

      private onChange = () => {
        this.setState({lastUpdated: Date.now()});
      };

      componentDidMount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            // each store has a common change event to subscribe to
            store.on('change', this.onChange);
          });
      };

      componentWillUnmount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            store.off('change', this.onChange);
          });
      };

      render() {
        return (
          <WrappedComponent
            lastUpdated={this.state.lastUpdated}
            {...this.props}
          />
        );
      }
    };
  };
}

SimpleStoreクラス

import AsyncStorage from '@react-native-community/async-storage';
import ee, {Emitter} from 'event-emitter';

interface SimpleStoreArgs {
  key?: string;
  defaultState?: {[key: string]: any};
}

export default class SimpleStore {
  constructor({key, defaultState}: SimpleStoreArgs) {
    if (key) {
      this.key = key;
      // hydrate here if you want w/ localState or AsyncStorage
    }
    if (defaultState) {
      this._state = {...defaultState, loaded: false};
    } else {
      this._state = {loaded: true};
    }
  }
  protected key: string = '';
  protected _state: {[key: string]: any} = {};
  protected eventEmitter: Emitter = ee({});
  public setState(newState: {[key: string]: any}) {
    this._state = {...this._state, ...newState};
    this.eventEmitter.emit('change');
    if (this.key) {
      // store on client w/ localState or AsyncStorage
    }
  }
  public get state() {
    return this._state;
  }
  public on(ev: string, fn:() => void) {
    this.eventEmitter.on(ev, fn);
  }
  public off(ev: string, fn:() => void) {
    this.eventEmitter.off(ev, fn);
  }
  public get loaded(): boolean {
    return !!this._state.loaded;
  }
}

使い方

フックの場合:

// use inside function like so
const someState = useStore(myStore);
someState.myProp = 'something';

HOCの場合:

// inside your code get/set your store and stuff just updates
const val = myStore.myProp;
myOtherStore.myProp = 'something';
// return your wrapped component like so
export default withStores(myStore)(MyComponent);

次のようにグローバルな変化の恩恵を受けるために、ストアをシングルトンとしてエクスポートすることを確認してください

class MyStore extends SimpleStore {
  public get someProp() {
    return this._state.someProp || '';
  }
  public set someProp(value: string) {
    this.setState({...this._state, someProp: value});
  }
}
// this is a singleton
const myStore = new MyStore();
export {myStore};

このアプローチは非常に単純で、私にとってはうまくいきます。私も大規模なチームで働いており、ReduxとMobXを使用していますが、それらも優れていると思いますが、定型文はたくさんあります。個人的には自分のアプローチが好きです。なぜなら、必要なときにシンプルにできるコードをたくさん嫌っていたからです。

So I guess my question is: do React components need to have state in order to rerender? Is there a way to force the component to update on demand without changing the state?

他の答えはあなたがどのようにできるかを説明しようとしました、しかし要点はあなたがそうすべきではないということです。キーを変更するというハッキーなソリューションでさえ、要点を見逃しています。Reactの力は、何かがいつレンダリングされるべきかを手動で管理する制御を放棄し、代わりに、何かが入力にどのようにマッピングされるべきかについて自分自身に関係することです。次に、入力のストリームを供給します。

手動で再レンダリングを強制する必要がある場合は、ほぼ確実に正しいことを行っていません。

コンポーネントを再レンダリングする方法はいくつかあります。

最も簡単な解決策は、forceUpdate()メソッドを使用することです。

this.forceUpdate()

もう1つの解決策は、state(nonUsedKey)に未使用のキーを作成し、このnonUsedKeyを更新してsetState関数を呼び出すことです。

this.setState({ nonUsedKey: Date.now() } );

または、現在のすべての状態を書き換えます。

this.setState(this.state);

小道具の変更は、コンポーネントの再レンダリングも提供します。

完全を期すために、機能コンポーネントでもこれを実現できます。

const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
// ...
forceUpdate();

または、再利用可能なフックとして:

const useForceUpdate = () => {
  const [, updateState] = useState();
  return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();

参照:https ://stackoverflow.com/a/53215514/2692307

強制更新メカニズムを使用することは、反応の考え方に反するため、依然として悪い習慣であることに注意してください。したがって、可能であればそれを避ける必要があります。

あなたはそれをいくつかの方法で行うことができます:

1.次のforceUpdate()方法を使用します。

メソッドを使用するときに発生する可能性のあるいくつかの不具合がありますforceUpdate()1つの例は、メソッドを無視し、falseを返すshouldComponentUpdate()かどうかに関係なくビューを再レンダリングすることです。shouldComponentUpdate()このため、forceUpdate()の使用は可能な限り避ける必要があります。

2.this.stateをsetState()メソッドに渡します

次のコード行は、前の例の問題を解決します。

this.setState(this.state);

実際には、これが行っているのは、現在の状態を現在の状態で上書きして、再レンダリングをトリガーすることだけです。これは必ずしも最善の方法ではありませんが、forceUpdate()メソッドを使用して発生する可能性のあるいくつかの不具合を克服します。

this.forceUpdate()は次のように使用できます。

       class MyComponent extends React.Component {



      handleButtonClick = ()=>{
          this.forceUpdate();
     }


 render() {

   return (
     <div>
      {Math.random()}
        <button  onClick={this.handleButtonClick}>
        Click me
        </button>
     </div>
    )
  }
}

 ReactDOM.render(<MyComponent /> , mountNode);

DOMの要素'Math.random'の部分は、setStateを使用してコンポーネントを再レンダリングした場合でものみ更新されます。

ここでのすべての答えは、理解のための質問を補足するものです。setState({})を使用せずにコンポーネントを再レンダリングすることは、forceUpdate()を使用することです。

上記のコードは、以下のようにsetStateで実行されます。

 class MyComponent extends React.Component {



             handleButtonClick = ()=>{
                this.setState({ });
              }


        render() {
         return (
  <div>
    {Math.random()}
    <button  onClick={this.handleButtonClick}>
      Click me
    </button>
  </div>
)
  }
 }

ReactDOM.render(<MyComponent /> , mountNode);

受け入れられた答えをバックアップするためのちょうど別の返信:-)

ReactはforceUpdate()、関数型プログラミングに対して非常に「これが唯一の方法」であるため、の使用を推奨していません。これは多くの場合問題ありませんが、多くのReact開発者はOOの背景を持っており、そのアプローチでは、観察可能なオブジェクトを聞くことはまったく問題ありません。

もしそうなら、あなたはおそらく、観察可能な「火」が起こったときに再レンダリングしなければならないことを知っているでしょう、そしてそのように、あなたは使うべきです、そしてそれはforceUpdate()実際にshouldComponentUpdate()はここに関係しないプラスです。

OOアプローチを採用するMobXのようなツールは、実際には表面下でこれを実行しています(実際にはMobXはrender()直接呼び出します)

forceUpdate();メソッドは機能しますが、使用することをお勧めしますsetState();

あなたが説明していることを達成するために、this.forceUpdate()を試してください。

別の方法は、呼び出しsetStateおよび状態の保持です。

this.setState(prevState=>({...prevState}));

forceUpdate()ですが、誰かがそれについて話すのを聞くたびに、これを使用してはいけないというフォローアップがあります。

forceUpdate()を避けるのが最善だと思いました。再レンダリングを強制する1つの方法は、render()の依存関係を一時的な外部変数に追加し、必要に応じてその変数の値を変更することです。

コード例は次のとおりです。

class Example extends Component{
   constructor(props){
      this.state = {temp:0};

      this.forceChange = this.forceChange.bind(this);
   }

   forceChange(){
      this.setState(prevState => ({
          temp: prevState.temp++
      })); 
   }

   render(){
      return(
         <div>{this.state.temp &&
             <div>
                  ... add code here ...
             </div>}
         </div>
      )
   }
}

再レンダリングを強制する必要がある場合は、this.forceChange()を呼び出します。

ES6-例を含めます。これは私にとって役に立ちました。

「shortifステートメント」では、次のような空の関数を渡すことができます。

isReady ? ()=>{} : onClick

これが最短のアプローチのようです。

()=>{}

Reactのドキュメントに記載されているように、、、、組み合わせuseEffectとして使用します。componentDidMountcomponentDidUpdatecomponentWillUnmount

のように動作するcomponentDidMountには、useEffectを次のように設定する必要があります。

  useEffect(() => console.log('mounted'), []);

最初の引数は、値の配列である2番目の引数に基づいて起動されるコールバックです。その2番目の引数の値のいずれかが変更された場合、内部で定義したコールバック関数が起動されますuseEffect

ただし、ここで示している例では、2番目の引数として空の配列を渡しています。これは変更されないため、コンポーネントがマウントされるときにコールバック関数が1回呼び出されます。

そのような要約useEffect空の値の代わりに、次のような引数がある場合:

 useEffect(() => {

  }, [props.lang]);

つまり、props.langが変更されるたびに、コールバック関数が呼び出されます。再レンダリングを起動する可能性のあるコールバック関数内のuseEffect状態を管理していない限り、はコンポーネントを実際には再レンダリングしません。

再レンダリングを実行する場合は、レンダリング関数の状態をで更新している必要がありますuseEffect

たとえば、ここでは、レンダリング機能はデフォルト言語として英語を表示することから始まり、私の使用効果では3秒後にその言語を変更するため、レンダリングが再レンダリングされ、「スペイン語」が表示され始めます。

function App() {
  const [lang, setLang] = useState("english");

  useEffect(() => {
    setTimeout(() => {
      setLang("spanish");
    }, 3000);
  }, []);

  return (
    <div className="App">
      <h1>Lang:</h1>
      <p>{lang}</p>
    </div>
  );
}

bruhローカル状態やデータの永続化を気にしない場合はlocation.reload()、ページを再読み込みし、明らかにコンポーネントを再レンダリングします。

詳細を確認するには、forceUpdate()を使用できます(forceUpdate())。