В этом компоненте реакции я пытаюсь отображать на экране по одному сообщению за раз. В этом сообщении есть выделенная жирным шрифтом подстрока, которую необходимо учитывать. Здесь 2 конечные точки,
message
для получения сообщения иbold
который извлекает то, что выделено жирным шрифтом (сstart
а такжеend
ключи к подстроке) оmessage
передается как параметр
Я попытался оптимизировать взаимодействие с пользователем, всегда имея два доступных сообщения, так что, когда пользователь нажимает «далее», уже есть сообщение для отображения и нет времени ожидания. Эти currentMessage
а также nextMessage
.
Я использовал два перехватчика useState, так как обнаружил, что цикл внутри перехватчика useEffect при извлечении несколько сложен. Все еще хотел бы иметь возможность это сделать.
Более того, я использовал два useEffect. Первый просто запускается один раз, чтобы получить начальное сообщение, а другой запускается при отображении страницы, а затем каждый раз, когда пользователь нажимает «Далее». Когда пользователь нажимает кнопку «Далее», приложение меняет текущее сообщение на то, которое находилось в «очереди».
const MessageBox = () => {
const [message, setCurrentMessage] = useState("");
const [bold, setCurrentBold] = useState("");
const [nextMessage, setNextMessage] = useState("");
const [nextBold, setNextBold] = useState("");
const [isGettingMessage, setIsGettingMessage] = useState(true);
useEffect(() => {
fetch(`https://myapi.com/message`)
.then((res) => res.json())
.then((res) => {
if (res.data) {
setCurrentMessage(res.data.message);
getBold(res.data.message, setCurrentBold);
} else {
console.log("handle err");
}
});
}, []);
useEffect(() => {
if (!isGettingMessage) return;
fetch(`https://myapi.com/message`)
.then((res) => res.json())
.then((res) => {
if (res.data) {
setNextMessage(res.data.message);
getBold(res.data.message, setNextBold);
} else {
console.log("handle err");
}
});
setIsGettingMessage(false);
}, [isGettingMessage]);
const getBold = (message, boldSetter) => {
if (!message) return;
fetch(`https://myapi.com/bold`, {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ content: message }),
})
.then((res) => res.json())
.then((res) => {
if (res?.data?.bold.length) {
boldSetter(res.data.bold);
return;
} else if (res.error) {
console.log("handle err");
}
boldSetter("");
});
};
const formatMessageAndBold = () => {
if (message && bold) {
return boldMessage;
} else if (message && !bold) {
return <span>{message}</span>;
}
};
const boldMessage = useMemo(
() => (
<p>
<span>{message.substring(0, bold.start)}</span>
<strong>{message.substring(bold.start, bold.end)}</strong>
<span>{message.substring(bold.end)}</span>
</p>
),
[message, bold.start, bold.end]
);
const messageAndBold = formatMessageAndBold();
const onClick = () => {
if (isGettingMessage) return;
setIsGettingMessage(true);
setCurrentMessage(nextMessage);
setCurrentBold(nextBold);
};
return (
<div>
{messageAndBold}
<button isLoading={nextMessage === message || !message} onClick={onClick}>
{message ? "Next Message" : "Loading..."}
</button>
</div>
);
};
export default MessageBox;
```
1 ответ
примечание: это заняло у меня больше времени, чем я ожидал, и могут быть некоторые синтаксические ошибки, но содержит общую идею
Как только ваш компонент начинает иметь сложную логику, это хороший повод начать его разбивать.
Здесь я предлагаю поместить вашу логику в несколько разных файлов:
constants.js
helpers.js
hooks.js
MessageBox.js
Примечательно, что в hooks.js
Я бы создал несколько пользовательских хуков для обработки вашей логики.
useQueryFullMessage
— обрабатывает получение сообщения + жирный шрифт и возврат результатаuseFullMessage
— используетuseQueryFullMessage
дважды, чтобы получить два полных сообщения и обрабатывает логику предварительной загрузки следующего полного сообщения.
Это возможная абстракция (среди многих), которую вы можете использовать.
Я тоже не верю в это useMemo
в этой ситуации требуется … если вы не собираетесь загружать много сообщений. Реагирование по умолчанию уже довольно оптимизировано.
//constants.js
const API_MESSAGE_URL = 'https://myapi.com/message';
const API_BOLD_URL = 'https://myapi.com/bold';
//helpers.js
import { API_MESSAGE_URL, API_BOLD_URL } from './constants';
const getMessage = async () => {
const res = await fetch(API_MESSAGE_URL);
const {data} = await res.json() ?? {};
return data?.message;
}
const getBold = async (message) => {
const res = await fetch(API_BOLD_URL, {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ content: message }),
});
const {data} = await res.json() ?? {};
return data?.bold;
}
export const getFullMessage = async () => {
const message = await getMessage();
if(!message) {
// handle error
return {};
}
const bold = await getBold(message);
if(!bold) {
// handle error
}
return {
message,
bold
};
}
//hooks.js
import { getFullMessage } from './helpers';
const useQueryFullMessage = () => {
const [fullMessaage, setFullMessage] = useState();
const [loading, setLoading] = useState(true);
const [preloadNextValue, togglePreloadNext] = useState(0);
useEffect(async () => {
if(!loading) {
setLoading(true);
}
const fullMessage = await getFullMessage();
// handle if bold not set
setFullMessage(fullMessage);
setLoading(false);
}, [preloadNextValue]);
return {
fullMessage,
loading,
preloadNext: () => togglePreloadNext(preloadNextValue + 1),
setFullMessage,
}
}
export const useFullMessage = () => {
const {
fullMessage: currentFullMessage,
loading: loadingCurrentFullMessage,
setFullMessage: setCurrentFullMessage
} = useQueryFullMessage();
const {
fullMessage: nextFullMessage,
loading: loadingNextFullMessage,
preloadNext
} = useQueryFullMessage();
const requestNextMessage = () => {
if(loadingCurrentFullMessage || loadingNextFullMessage) {
return;
}
setCurrentFullMessage(nextFullMessage);
preloadNext();
}
return {
currentFullMessage,
loadingCurrentFullMessage,
nextFullMessage,
loadingNextFullMessage,
requestNextMessage
};
}
// MessageBox.js
import { useFullMesage } from './hooks';
const MessageBox = () => {
const {
currentFullMessage,
loadingCurrentFullMessage,
nextFullMessage,
loadingNextFullMessage,
requestNextMessage
} = useFullMessage();
const { message, bold } = fullMessage;
// TODO: check if message and bold are set.
const formatMessageAndBold = () => {
if (message && bold) {
return <p>
<span>{message.substring(0, bold.start)}</span>
<strong>{message.substring(bold.start, bold.end)}</strong>
<span>{message.substring(bold.end)}</span>
</p>;
} else if (message && !bold) {
return <span>{message}</span>;
}
};
return (
<div>
{loadingCurrentFullMessage ? "Loading..." : formatMessageAndBold()}
<button isLoading={loadingNextFullMessage} onClick={requestNextMessage}>
{!loadingNextFullMessage ? "Next Message" : "Loading..."}
</button>
</div>
);
}
```