Movatterモバイル変換


[0]ホーム

URL:


Is this page useful?

Lifecycle of Reactive Effects

Effects have a different lifecycle from components. Components may mount, update, or unmount. An Effect can only do two things: to start synchronizing something, and later to stop synchronizing it. This cycle can happen multiple times if your Effect depends on props and state that change over time. React provides a linter rule to check that you’ve specified your Effect’s dependencies correctly. This keeps your Effect synchronized to the latest props and state.

You will learn

  • How an Effect’s lifecycle is different from a component’s lifecycle
  • How to think about each individual Effect in isolation
  • When your Effect needs to re-synchronize, and why
  • How your Effect’s dependencies are determined
  • What it means for a value to be reactive
  • What an empty dependency array means
  • How React verifies your dependencies are correct with a linter
  • What to do when you disagree with the linter

The lifecycle of an Effect

Every React component goes through the same lifecycle:

  • A componentmounts when it’s added to the screen.
  • A componentupdates when it receives new props or state, usually in response to an interaction.
  • A componentunmounts when it’s removed from the screen.

It’s a good way to think about components, butnot about Effects. Instead, try to think about each Effect independently from your component’s lifecycle. An Effect describes how tosynchronize an external system to the current props and state. As your code changes, synchronization will need to happen more or less often.

To illustrate this point, consider this Effect connecting your component to a chat server:

constserverUrl ='https://localhost:1234';

functionChatRoom({roomId}){
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
},[roomId]);
// ...
}

Your Effect’s body specifies how tostart synchronizing:

// ...
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
// ...

The cleanup function returned by your Effect specifies how tostop synchronizing:

// ...
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
// ...

Intuitively, you might think that React wouldstart synchronizing when your component mounts andstop synchronizing when your component unmounts. However, this is not the end of the story! Sometimes, it may also be necessary tostart and stop synchronizing multiple times while the component remains mounted.

Let’s look atwhy this is necessary,when it happens, andhow you can control this behavior.

Note

Some Effects don’t return a cleanup function at all.More often than not, you’ll want to return one—but if you don’t, React will behave as if you returned an empty cleanup function.

Why synchronization may need to happen more than once

Imagine thisChatRoom component receives aroomId prop that the user picks in a dropdown. Let’s say that initially the user picks the"general" room as theroomId. Your app displays the"general" chat room:

constserverUrl ='https://localhost:1234';

functionChatRoom({roomId/* "general" */}){
// ...
return<h1>Welcome to the{roomId} room!</h1>;
}

After the UI is displayed, React will run your Effect tostart synchronizing. It connects to the"general" room:

functionChatRoom({roomId/* "general" */}){
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);// Connects to the "general" room
connection.connect();
return()=>{
connection.disconnect();// Disconnects from the "general" room
};
},[roomId]);
// ...

So far, so good.

Later, the user picks a different room in the dropdown (for example,"travel"). First, React will update the UI:

functionChatRoom({roomId/* "travel" */}){
// ...
return<h1>Welcome to the{roomId} room!</h1>;
}

Think about what should happen next. The user sees that"travel" is the selected chat room in the UI. However, the Effect that ran the last time is still connected to the"general" room.TheroomId prop has changed, so what your Effect did back then (connecting to the"general" room) no longer matches the UI.

At this point, you want React to do two things:

  1. Stop synchronizing with the oldroomId (disconnect from the"general" room)
  2. Start synchronizing with the newroomId (connect to the"travel" room)

Luckily, you’ve already taught React how to do both of these things! Your Effect’s body specifies how to start synchronizing, and your cleanup function specifies how to stop synchronizing. All that React needs to do now is to call them in the correct order and with the correct props and state. Let’s see how exactly that happens.

How React re-synchronizes your Effect

Recall that yourChatRoom component has received a new value for itsroomId prop. It used to be"general", and now it is"travel". React needs to re-synchronize your Effect to re-connect you to a different room.

Tostop synchronizing, React will call the cleanup function that your Effect returned after connecting to the"general" room. SinceroomId was"general", the cleanup function disconnects from the"general" room:

functionChatRoom({roomId/* "general" */}){
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);// Connects to the "general" room
connection.connect();
return()=>{
connection.disconnect();// Disconnects from the "general" room
};
// ...

Then React will run the Effect that you’ve provided during this render. This time,roomId is"travel" so it willstart synchronizing to the"travel" chat room (until its cleanup function is eventually called too):

functionChatRoom({roomId/* "travel" */}){
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);// Connects to the "travel" room
connection.connect();
// ...

Thanks to this, you’re now connected to the same room that the user chose in the UI. Disaster averted!

Every time after your component re-renders with a differentroomId, your Effect will re-synchronize. For example, let’s say the user changesroomId from"travel" to"music". React will againstop synchronizing your Effect by calling its cleanup function (disconnecting you from the"travel" room). Then it willstart synchronizing again by running its body with the newroomId prop (connecting you to the"music" room).

Finally, when the user goes to a different screen,ChatRoom unmounts. Now there is no need to stay connected at all. React willstop synchronizing your Effect one last time and disconnect you from the"music" chat room.

Thinking from the Effect’s perspective

Let’s recap everything that’s happened from theChatRoom component’s perspective:

  1. ChatRoom mounted withroomId set to"general"
  2. ChatRoom updated withroomId set to"travel"
  3. ChatRoom updated withroomId set to"music"
  4. ChatRoom unmounted

During each of these points in the component’s lifecycle, your Effect did different things:

  1. Your Effect connected to the"general" room
  2. Your Effect disconnected from the"general" room and connected to the"travel" room
  3. Your Effect disconnected from the"travel" room and connected to the"music" room
  4. Your Effect disconnected from the"music" room

Now let’s think about what happened from the perspective of the Effect itself:

useEffect(()=>{
// Your Effect connected to the room specified with roomId...
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
// ...until it disconnected
connection.disconnect();
};
},[roomId]);

This code’s structure might inspire you to see what happened as a sequence of non-overlapping time periods:

  1. Your Effect connected to the"general" room (until it disconnected)
  2. Your Effect connected to the"travel" room (until it disconnected)
  3. Your Effect connected to the"music" room (until it disconnected)

Previously, you were thinking from the component’s perspective. When you looked from the component’s perspective, it was tempting to think of Effects as “callbacks” or “lifecycle events” that fire at a specific time like “after a render” or “before unmount”. This way of thinking gets complicated very fast, so it’s best to avoid.

Instead, always focus on a single start/stop cycle at a time. It shouldn’t matter whether a component is mounting, updating, or unmounting. All you need to do is to describe how to start synchronization and how to stop it. If you do it well, your Effect will be resilient to being started and stopped as many times as it’s needed.

This might remind you how you don’t think whether a component is mounting or updating when you write the rendering logic that creates JSX. You describe what should be on the screen, and Reactfigures out the rest.

How React verifies that your Effect can re-synchronize

Here is a live example that you can play with. Press “Open chat” to mount theChatRoom component:

Fork
import{useState,useEffect}from'react';import{createConnection}from'./chat.js';constserverUrl ='https://localhost:1234';functionChatRoom({roomId}){useEffect(()=>{constconnection =createConnection(serverUrl,roomId);connection.connect();return()=>connection.disconnect();},[roomId]);return<h1>Welcome to the{roomId} room!</h1>;}exportdefaultfunctionApp(){const[roomId,setRoomId] =useState('general');const[show,setShow] =useState(false);return(<><label>        Choose the chat room:{' '}<selectvalue={roomId}onChange={e=>setRoomId(e.target.value)}><optionvalue="general">general</option><optionvalue="travel">travel</option><optionvalue="music">music</option></select></label><buttononClick={()=>setShow(!show)}>{show ?'Close chat' :'Open chat'}</button>{show &&<hr/>}{show &&<ChatRoomroomId={roomId}/>}</>);}

Notice that when the component mounts for the first time, you see three logs:

  1. ✅ Connecting to "general" room at https://localhost:1234...(development-only)
  2. ❌ Disconnected from "general" room at https://localhost:1234.(development-only)
  3. ✅ Connecting to "general" room at https://localhost:1234...

The first two logs are development-only. In development, React always remounts each component once.

React verifies that your Effect can re-synchronize by forcing it to do that immediately in development. This might remind you of opening a door and closing it an extra time to check if the door lock works. React starts and stops your Effect one extra time in development to checkyou’ve implemented its cleanup well.

The main reason your Effect will re-synchronize in practice is if some data it uses has changed. In the sandbox above, change the selected chat room. Notice how, when theroomId changes, your Effect re-synchronizes.

However, there are also more unusual cases in which re-synchronization is necessary. For example, try editing theserverUrl in the sandbox above while the chat is open. Notice how the Effect re-synchronizes in response to your edits to the code. In the future, React may add more features that rely on re-synchronization.

How React knows that it needs to re-synchronize the Effect

You might be wondering how React knew that your Effect needed to re-synchronize afterroomId changes. It’s becauseyou told React that its code depends onroomId by including it in thelist of dependencies:

functionChatRoom({roomId}){// The roomId prop may change over time
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);// This Effect reads roomId
connection.connect();
return()=>{
connection.disconnect();
};
},[roomId]);// So you tell React that this Effect "depends on" roomId
// ...

Here’s how this works:

  1. You knewroomId is a prop, which means it can change over time.
  2. You knew that your Effect readsroomId (so its logic depends on a value that may change later).
  3. This is why you specified it as your Effect’s dependency (so that it re-synchronizes whenroomId changes).

Every time after your component re-renders, React will look at the array of dependencies that you have passed. If any of the values in the array is different from the value at the same spot that you passed during the previous render, React will re-synchronize your Effect.

For example, if you passed["general"] during the initial render, and later you passed["travel"] during the next render, React will compare"general" and"travel". These are different values (compared withObject.is), so React will re-synchronize your Effect. On the other hand, if your component re-renders butroomId has not changed, your Effect will remain connected to the same room.

Each Effect represents a separate synchronization process

Resist adding unrelated logic to your Effect only because this logic needs to run at the same time as an Effect you already wrote. For example, let’s say you want to send an analytics event when the user visits the room. You already have an Effect that depends onroomId, so you might feel tempted to add the analytics call there:

functionChatRoom({roomId}){
useEffect(()=>{
logVisit(roomId);
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
},[roomId]);
// ...
}

But imagine you later add another dependency to this Effect that needs to re-establish the connection. If this Effect re-synchronizes, it will also calllogVisit(roomId) for the same room, which you did not intend. Logging the visitis a separate process from connecting. Write them as two separate Effects:

functionChatRoom({roomId}){
useEffect(()=>{
logVisit(roomId);
},[roomId]);

useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);
// ...
},[roomId]);
// ...
}

Each Effect in your code should represent a separate and independent synchronization process.

In the above example, deleting one Effect wouldn’t break the other Effect’s logic. This is a good indication that they synchronize different things, and so it made sense to split them up. On the other hand, if you split up a cohesive piece of logic into separate Effects, the code may look “cleaner” but will bemore difficult to maintain. This is why you should think whether the processes are same or separate, not whether the code looks cleaner.

Effects “react” to reactive values

Your Effect reads two variables (serverUrl androomId), but you only specifiedroomId as a dependency:

constserverUrl ='https://localhost:1234';

functionChatRoom({roomId}){
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
},[roomId]);
// ...
}

Why doesn’tserverUrl need to be a dependency?

This is because theserverUrl never changes due to a re-render. It’s always the same no matter how many times the component re-renders and why. SinceserverUrl never changes, it wouldn’t make sense to specify it as a dependency. After all, dependencies only do something when they change over time!

On the other hand,roomId may be different on a re-render.Props, state, and other values declared inside the component arereactive because they’re calculated during rendering and participate in the React data flow.

IfserverUrl was a state variable, it would be reactive. Reactive values must be included in dependencies:

functionChatRoom({roomId}){// Props change over time
const[serverUrl,setServerUrl] =useState('https://localhost:1234');// State may change over time

useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);// Your Effect reads props and state
connection.connect();
return()=>{
connection.disconnect();
};
},[roomId,serverUrl]);// So you tell React that this Effect "depends on" on props and state
// ...
}

By includingserverUrl as a dependency, you ensure that the Effect re-synchronizes after it changes.

Try changing the selected chat room or edit the server URL in this sandbox:

Fork
import{useState,useEffect}from'react';import{createConnection}from'./chat.js';functionChatRoom({roomId}){const[serverUrl,setServerUrl] =useState('https://localhost:1234');useEffect(()=>{constconnection =createConnection(serverUrl,roomId);connection.connect();return()=>connection.disconnect();},[roomId,serverUrl]);return(<><label>        Server URL:{' '}<inputvalue={serverUrl}onChange={e=>setServerUrl(e.target.value)}/></label><h1>Welcome to the{roomId} room!</h1></>);}exportdefaultfunctionApp(){const[roomId,setRoomId] =useState('general');return(<><label>        Choose the chat room:{' '}<selectvalue={roomId}onChange={e=>setRoomId(e.target.value)}><optionvalue="general">general</option><optionvalue="travel">travel</option><optionvalue="music">music</option></select></label><hr/><ChatRoomroomId={roomId}/></>);}

Whenever you change a reactive value likeroomId orserverUrl, the Effect re-connects to the chat server.

What an Effect with empty dependencies means

What happens if you move bothserverUrl androomId outside the component?

constserverUrl ='https://localhost:1234';
constroomId ='general';

functionChatRoom(){
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
},[]);// ✅ All dependencies declared
// ...
}

Now your Effect’s code does not useany reactive values, so its dependencies can be empty ([]).

Thinking from the component’s perspective, the empty[] dependency array means this Effect connects to the chat room only when the component mounts, and disconnects only when the component unmounts. (Keep in mind that React would stillre-synchronize it an extra time in development to stress-test your logic.)

Fork
import{useState,useEffect}from'react';import{createConnection}from'./chat.js';constserverUrl ='https://localhost:1234';constroomId ='general';functionChatRoom(){useEffect(()=>{constconnection =createConnection(serverUrl,roomId);connection.connect();return()=>connection.disconnect();},[]);return<h1>Welcome to the{roomId} room!</h1>;}exportdefaultfunctionApp(){const[show,setShow] =useState(false);return(<><buttononClick={()=>setShow(!show)}>{show ?'Close chat' :'Open chat'}</button>{show &&<hr/>}{show &&<ChatRoom/>}</>);}

However, if youthink from the Effect’s perspective, you don’t need to think about mounting and unmounting at all. What’s important is you’ve specified what your Effect does to start and stop synchronizing. Today, it has no reactive dependencies. But if you ever want the user to changeroomId orserverUrl over time (and they would become reactive), your Effect’s code won’t change. You will only need to add them to the dependencies.

All variables declared in the component body are reactive

Props and state aren’t the only reactive values. Values that you calculate from them are also reactive. If the props or state change, your component will re-render, and the values calculated from them will also change. This is why all variables from the component body used by the Effect should be in the Effect dependency list.

Let’s say that the user can pick a chat server in the dropdown, but they can also configure a default server in settings. Suppose you’ve already put the settings state in acontext so you read thesettings from that context. Now you calculate theserverUrl based on the selected server from props and the default server:

functionChatRoom({roomId,selectedServerUrl}){// roomId is reactive
constsettings =useContext(SettingsContext);// settings is reactive
constserverUrl =selectedServerUrl ??settings.defaultServerUrl;// serverUrl is reactive
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);// Your Effect reads roomId and serverUrl
connection.connect();
return()=>{
connection.disconnect();
};
},[roomId,serverUrl]);// So it needs to re-synchronize when either of them changes!
// ...
}

In this example,serverUrl is not a prop or a state variable. It’s a regular variable that you calculate during rendering. But it’s calculated during rendering, so it can change due to a re-render. This is why it’s reactive.

All values inside the component (including props, state, and variables in your component’s body) are reactive. Any reactive value can change on a re-render, so you need to include reactive values as Effect’s dependencies.

In other words, Effects “react” to all values from the component body.

Deep Dive

Can global or mutable values be dependencies?

Mutable values (including global variables) aren’t reactive.

A mutable value likelocation.pathname can’t be a dependency. It’s mutable, so it can change at any time completely outside of the React rendering data flow. Changing it wouldn’t trigger a re-render of your component. Therefore, even if you specified it in the dependencies, Reactwouldn’t know to re-synchronize the Effect when it changes. This also breaks the rules of React because reading mutable data during rendering (which is when you calculate the dependencies) breakspurity of rendering. Instead, you should read and subscribe to an external mutable value withuseSyncExternalStore.

A mutable value likeref.current or things you read from it also can’t be a dependency. The ref object returned byuseRef itself can be a dependency, but itscurrent property is intentionally mutable. It lets youkeep track of something without triggering a re-render. But since changing it doesn’t trigger a re-render, it’s not a reactive value, and React won’t know to re-run your Effect when it changes.

As you’ll learn below on this page, a linter will check for these issues automatically.

React verifies that you specified every reactive value as a dependency

If your linter isconfigured for React, it will check that every reactive value used by your Effect’s code is declared as its dependency. For example, this is a lint error because bothroomId andserverUrl are reactive:

Fork
import{useState,useEffect}from'react';import{createConnection}from'./chat.js';functionChatRoom({roomId}){// roomId is reactiveconst[serverUrl,setServerUrl] =useState('https://localhost:1234');// serverUrl is reactiveuseEffect(()=>{constconnection =createConnection(serverUrl,roomId);connection.connect();return()=>connection.disconnect();},[]);// <-- Something's wrong here!return(<><label>        Server URL:{' '}<inputvalue={serverUrl}onChange={e=>setServerUrl(e.target.value)}/></label><h1>Welcome to the{roomId} room!</h1></>);}exportdefaultfunctionApp(){const[roomId,setRoomId] =useState('general');return(<><label>        Choose the chat room:{' '}<selectvalue={roomId}onChange={e=>setRoomId(e.target.value)}><optionvalue="general">general</option><optionvalue="travel">travel</option><optionvalue="music">music</option></select></label><hr/><ChatRoomroomId={roomId}/></>);}

This may look like a React error, but really React is pointing out a bug in your code. BothroomId andserverUrl may change over time, but you’re forgetting to re-synchronize your Effect when they change. You will remain connected to the initialroomId andserverUrl even after the user picks different values in the UI.

To fix the bug, follow the linter’s suggestion to specifyroomId andserverUrl as dependencies of your Effect:

functionChatRoom({roomId}){// roomId is reactive
const[serverUrl,setServerUrl] =useState('https://localhost:1234');// serverUrl is reactive
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
},[serverUrl,roomId]);// ✅ All dependencies declared
// ...
}

Try this fix in the sandbox above. Verify that the linter error is gone, and the chat re-connects when needed.

Note

In some cases, Reactknows that a value never changes even though it’s declared inside the component. For example, theset function returned fromuseState and the ref object returned byuseRef arestable—they are guaranteed to not change on a re-render. Stable values aren’t reactive, so you may omit them from the list. Including them is allowed: they won’t change, so it doesn’t matter.

What to do when you don’t want to re-synchronize

In the previous example, you’ve fixed the lint error by listingroomId andserverUrl as dependencies.

However, you could instead “prove” to the linter that these values aren’t reactive values, i.e. that theycan’t change as a result of a re-render. For example, ifserverUrl androomId don’t depend on rendering and always have the same values, you can move them outside the component. Now they don’t need to be dependencies:

constserverUrl ='https://localhost:1234';// serverUrl is not reactive
constroomId ='general';// roomId is not reactive

functionChatRoom(){
useEffect(()=>{
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
},[]);// ✅ All dependencies declared
// ...
}

You can also move theminside the Effect. They aren’t calculated during rendering, so they’re not reactive:

functionChatRoom(){
useEffect(()=>{
constserverUrl ='https://localhost:1234';// serverUrl is not reactive
constroomId ='general';// roomId is not reactive
constconnection =createConnection(serverUrl,roomId);
connection.connect();
return()=>{
connection.disconnect();
};
},[]);// ✅ All dependencies declared
// ...
}

Effects are reactive blocks of code. They re-synchronize when the values you read inside of them change. Unlike event handlers, which only run once per interaction, Effects run whenever synchronization is necessary.

You can’t “choose” your dependencies. Your dependencies must include everyreactive value you read in the Effect. The linter enforces this. Sometimes this may lead to problems like infinite loops and to your Effect re-synchronizing too often. Don’t fix these problems by suppressing the linter! Here’s what to try instead:

  • Check that your Effect represents an independent synchronization process. If your Effect doesn’t synchronize anything,it might be unnecessary. If it synchronizes several independent things,split it up.

  • If you want to read the latest value of props or state without “reacting” to it and re-synchronizing the Effect, you can split your Effect into a reactive part (which you’ll keep in the Effect) and a non-reactive part (which you’ll extract into something called anEffect Event).Read about separating Events from Effects.

  • Avoid relying on objects and functions as dependencies. If you create objects and functions during rendering and then read them from an Effect, they will be different on every render. This will cause your Effect to re-synchronize every time.Read more about removing unnecessary dependencies from Effects.

Pitfall

The linter is your friend, but its powers are limited. The linter only knows when the dependencies arewrong. It doesn’t knowthe best way to solve each case. If the linter suggests a dependency, but adding it causes a loop, it doesn’t mean the linter should be ignored. You need to change the code inside (or outside) the Effect so that that value isn’t reactive and doesn’tneed to be a dependency.

If you have an existing codebase, you might have some Effects that suppress the linter like this:

useEffect(()=>{
// ...
// 🔴 Avoid suppressing the linter like this:
// eslint-ignore-next-line react-hooks/exhaustive-deps
},[]);

On thenextpages, you’ll learn how to fix this code without breaking the rules. It’s always worth fixing!

Recap

  • Components can mount, update, and unmount.
  • Each Effect has a separate lifecycle from the surrounding component.
  • Each Effect describes a separate synchronization process that canstart andstop.
  • When you write and read Effects, think from each individual Effect’s perspective (how to start and stop synchronization) rather than from the component’s perspective (how it mounts, updates, or unmounts).
  • Values declared inside the component body are “reactive”.
  • Reactive values should re-synchronize the Effect because they can change over time.
  • The linter verifies that all reactive values used inside the Effect are specified as dependencies.
  • All errors flagged by the linter are legitimate. There’s always a way to fix the code to not break the rules.

Try out some challenges

Challenge 1 of 5:
Fix reconnecting on every keystroke

In this example, theChatRoom component connects to the chat room when the component mounts, disconnects when it unmounts, and reconnects when you select a different chat room. This behavior is correct, so you need to keep it working.

However, there is a problem. Whenever you type into the message box input at the bottom,ChatRoomalso reconnects to the chat. (You can notice this by clearing the console and typing into the input.) Fix the issue so that this doesn’t happen.

Fork
import{useState,useEffect}from'react';import{createConnection}from'./chat.js';constserverUrl ='https://localhost:1234';functionChatRoom({roomId}){const[message,setMessage] =useState('');useEffect(()=>{constconnection =createConnection(serverUrl,roomId);connection.connect();return()=>connection.disconnect();});return(<><h1>Welcome to the{roomId} room!</h1><inputvalue={message}onChange={e=>setMessage(e.target.value)}/></>);}exportdefaultfunctionApp(){const[roomId,setRoomId] =useState('general');return(<><label>        Choose the chat room:{' '}<selectvalue={roomId}onChange={e=>setRoomId(e.target.value)}><optionvalue="general">general</option><optionvalue="travel">travel</option><optionvalue="music">music</option></select></label><hr/><ChatRoomroomId={roomId}/></>);}


[8]ページ先頭

©2009-2025 Movatter.jp