問題描述
在 reactJS 中動態設置圖像 src (Setting image src dynamically in reactJS)
我正在嘗試在 reactJS 中動態設置圖像 src,我使用的是打字稿,關於圖像的代碼如下。
這只是我的代碼的一部分,展示了邏輯我正在嘗試更新我的應用程序中的天氣圖標。
如果您有其他意見,請不要害羞分享它們,我非常樂意接受任何建設性的批評,但主要是請幫助解決我在這裡遇到的主要問題。
const [Icon, setIcon] = useState("");
useEffect(() => {
switch (Icon.substr(0, 2)) {
case "01":
setIcon(sun);
break;
case "50":
setIcon(fog);
break;
case "09":
setIcon(drizzle);
break;
case "11":
setIcon(storm);
break;
case "09" || "13" || "10":
setIcon(rain);
break;
case "13":
setIcon(snow);
break;
case "02" || "03" || "04":
setIcon(clouds);
break;
default:
setIcon(sun);
}
}, [Icon]);
return(<img src={Icon} />)
‑‑‑完整代碼‑‑‑‑
import React, { useState, useEffect } from "react";
import styled from "styled‑components";
import Header from "../components/Header";
import Footer from "../components/Footer";
import axios from "axios";
import Clock from "react‑live‑clock";
import Loading from "../components/PageLoader";
import rain from "../images/rain.svg";
import drizzle from "../images/drizzle.svg";
import fog from "../images/fog.svg";
import clouds from "../images/cloud.svg";
import snow from "../images/snowflake.svg";
import storm from "../images/storm.svg";
import sun from "../images/sun.svg";
export default function Home() {
let cityName: string;
const [city, setCity] = useState("amman");
const [Icon, setIcon] = useState("");
const changeC = document.getElementById("C");
const changeF = document.getElementById("F");
const [triggerEffect, setTriggerEffect] = useState(0);
const textChange = (event: any) => {
cityName = event.target.value;
};
const [weatherData, setWeatherData] = useState(Object);
const time = Date();
useEffect(() => {
const fetchData = async () => {
await axios
.get(`http://localhost:5000/app/weather?address=${city}`)
.then((response) => {
setWeatherData(response.data);
setIcon(response.data.weather["0"].icon);
});
};
fetchData();
}, [triggerEffect]);
const [celsius, setCelsius] = useState(true);
const onSubmitText = (event: any) => {
event.preventDefault();
setTriggerEffect(triggerEffect + 1);
if (cityName) {
setCity(cityName);
}
};
function convertCels() {
setCelsius(true);
console.log(changeF + " " + changeC);
if (changeC && changeF) {
changeC.style.color = "#0000ff";
changeF.style.color = "000000";
}
}
function convertFeh() {
setCelsius(false);
console.log(changeF + " " + changeC);
if (changeC && changeF) {
changeC.style.color = "#000000";
changeF.style.color = "0000ff";
}
}
useEffect(() => {
switch (Icon.substr(0, 2)) {
case "01":
setIcon(sun);
break;
case "50":
setIcon(fog);
break;
case "09":
setIcon(drizzle);
break;
case "11":
setIcon(storm);
break;
case "09" || "13" || "10":
setIcon(rain);
break;
case "13":
setIcon(snow);
break;
case "02" || "03" || "04":
setIcon(clouds);
break;
default:
setIcon(sun);
}
}, [Icon]);
return weatherData["name"] && Icon ? (
<MainContainer>
<Header />
<SearchBoxDiv>
<Button type="submit" onClick={onSubmitText}></Button>
<SearchBox
id="text"
type="text"
placeholder="Location"
onChange={textChange}
></SearchBox>
</SearchBoxDiv>
<MainMidContainer>
<SecMidContainer>
<Vector src={Icon} />
</SecMidContainer>
<SecMidContainer>
<CityName>{weatherData["name"]}</CityName>
<Time>
{time.toString().substr(0, 3) + " "}
<Clock format={"HH:mm"} ticking={true} timezone={"Asia/Amman"} />
</Time>
<Condition>{weatherData.weather["0"].description}</Condition>
<Temp id="temp">
{celsius
? parseInt(weatherData.main["temp"]) ‑ 270 + " C"
: parseInt(
parseInt(weatherData.main["temp"]) * (9 / 5) ‑ 459 + "",
) + " F"}
{" "}
<SuperScriptC
onClick={() => {
convertCels();
}}
>
C
</SuperScriptC>
{" "}
<SuperScriptF
id="F"
onClick={() => {
convertFeh();
}}
>
F
</SuperScriptF>
</Temp>
</SecMidContainer>
</MainMidContainer>
<Footer />
</MainContainer>
) : (
<Loading />
);
}
const MainContainer = styled.div`
width: 1400px;
height: 100vh;
display: flex;
flex‑direction: column;
align‑items: center;
justify‑content: space‑between;
`;
const SearchBoxDiv = styled.form`
width: 500px;
height: 55px;
display: flex;
align‑self: center;
`;
const SearchBox = styled.input`
width: 497px;
height: 54px;
background: #ffffff;
border: 1px solid #000000;
box‑sizing: border‑box;
padding: 15px;
`;
const MainMidContainer = styled.div`
width: 900px;
heigh: 330px;
display: flex;
flex‑direction: row;
justify‑content: space‑between;
align‑self: center;
`;
const SecMidContainer = styled.div`
width: 40%;
height: 330px;
display: flex;
flex‑direction: column;
`;
const Vector = styled.img`
height: 330px;
width: 330px;
`;
const CityName = styled.p`
font‑family: Roboto;
font‑style: normal;
font‑weight: 900;
font‑size: 40px;
line‑height: 47px;
`;
const Time = styled.p`
font‑family: Roboto;
font‑style: normal;
font‑weight: normal;
font‑size: 40px;
line‑height: 47px;
`;
const Condition = styled.p`
font‑family: Roboto;
font‑style: normal;
font‑weight: normal;
font‑size: 30px;
line‑height: 35px;
`;
const Temp = styled.p`
font‑family: Roboto;
font‑style: normal;
font‑weight: normal;
font‑size: 100px;
line‑height: 117px;
margin‑top: 15 %;
`;
const SuperScriptC = styled.sup`
font‑family: Roboto;
font‑style: normal;
font‑weight: normal;
font‑size: 40px;
line‑height: 47px;
`;
const SuperScriptF = styled.sup`
font‑family: Roboto;
font‑style: normal;
font‑weight: normal;
font‑size: 40px;
line‑height: 47px;
`;
const Button = styled.input`
background‑color: transparent;
color: transparent;
border: none;
`;
參考解法
方法 1:
Problem: useEffect Dependencies
Your useEffect
has Icon
as a dependency but it is also setting Icon
. So every time that it changes the icon it will run again. There is potential for an infinite loop with this setup. Currently what's happening is that is always ends up on sun
from the default
case because whenever you set it to something else, it triggers a re‑run with the new value.
Solution 1: Better Dependencies
We want to reset the Icon
whenever the weatherData
changes. Rather than calling setIcon(response.data.weather["0"].icon)
(delete that line), we use the icon from the data to trigger the effect. The data is already stored in state as weatherData
.
// you need to fix your initial value of weatherData, but I think this will work
const weatherIcon = weatherData?.weather?.[0]?.icon;
useEffect(() => {
switch (weatherIcon.substr(0, 2)) {
case "01":
setIcon(sun);
break;
/* ... */
}
}, [weatherIcon]);
Solution 2: Pure Function
We are just choosing an icon based on a string
value in the API response. This does not need to be an effect. We can define a function that finds the icon for a given string
and call that function ourselves at the appropriate time. This function also saves you from having to write break
a bunch of times because we are calling return
.
const findIcon = (weatherIcon: string) => {
switch (weatherIcon.substr(0, 2)) {
case "01":
return sun;
case "50":
return fog;
/* ... */
default:
return sun;
}
}
You would call this function in your fetch
effect and delete the icon update effect.
.then((response) => {
setWeatherData(response.data);
setIcon(findIcon(response.data.weather["0"].icon));
});
There are some other issues with your code, but this should fix the icon situation.
(by Moe Atharbeh、Linda Paiste)