基于MVC架构的Pokemon卡片背景色无法渲染问题排查
问题描述
采用MVC架构开发宝可梦项目,通过PokeAPI获取数据,尝试从typeColor常量中渲染卡片背景色,控制台显示styleCard的颜色值已修改,但UI中背景色未生效。项目包含model.js、controller.js、resultView.js和View.js四个文件,代码如下:
model.js
export const state = { pokemon: {}, search: { query: '', results: [], page: 1, resultsPerPage: 100, }, }; const fetchJson = (url) => fetch(url) .then((r) => r.json()) .catch(console.log); export const loadPokemon = async () => { const [arrayA, arrayB] = await Promise.all([ Promise.all( new Array(1008) .fill(0) .map((_, i) => fetchJson(`https://pokeapi.co/api/v2/pokemon/${i + 1}`)) ), Promise.all( new Array(151) .fill(0) .map((_, i) => fetchJson(`https://pokeapi.co/api/v2/pokemon-species/${i + 1}`) ) ), ]); state.pokemon = arrayA.map((poke) => { return { name: poke.name, image: poke.sprites.front_default, types: poke.types, }; }); console.log(arrayA); console.log(arrayB); }; export const typeColor = { bug: '#26de81', dragon: '#ffeaa7', electric: '#fed330', fairy: '#FF0069', fighting: '#30336b', fire: '#f0932b', flying: '#81ecec', grass: '#00b894', ground: '#EFB549', ghost: '#a55eea', ice: '#74b9ff', normal: '#95afc0', poison: '#6c5ce7', psychic: '#a29bfe', rock: '#2d3436', water: '#0190FF', };
controller.js
import * as model from './model.js'; import resultView from './views/resultsView.js'; import View from './Views/View.js'; const controlPokemon = async function () { try { await model.loadPokemon(); resultView.render(model.state.pokemon); const typeData = model.state.pokemon.map((poke) => { return poke.types[0].type.name; }); console.log(typeData); const themeColor = model.typeColor[typeData[0]]; resultView.styleCard(themeColor); } catch (err) { console.error(err); } }; const init = function () { resultView.addHandlerRender(controlPokemon); }; init();
resultView.js
import View from './View.js'; class ResultsView extends View { _parentElement = document.querySelector('.results'); _card = document.querySelector('.card'); _generateMarkup() { console.log(this._data); return this._data.map(this._generateMarkupPreview).join(''); } addHandlerRender(handler) { ['load'].forEach((ev) => window.addEventListener(ev, handler)); } styleCard(color){ return (this._card.style.background = `radial-gradient(circle at 50% 0%, ${color} 36%, #ffffff 36%)`); }; _generateMarkupPreview(result) { return ` <li class="preview"> <a class="preview__link preview__link--active" href="#2343"></a> <div class="card"> <p class="hp"><span>HP</span>80</p> <figure class="preview__fig"> <img class="fig" src="${result.image}" /> </figure> <h4 class="txt-description">${result.name[0].toUpperCase() + result.name.slice(1) }</h4> <div class="types"> <span>${result.types[0]?.type.name[0].toUpperCase() + result.types[0].type.name.slice(1) }</span> <span>${result.types[1]?.type.name}</span> </div> <div class="stats"> <div> <h3>60</h3> <p>Attack</p> </div> <div> <h3>60</h3> <p>Defense</p> </div> <div> <h3>60</h3> <p>Speed</p> </div> </div> </div> </li>`; } } export default new ResultsView();
View.js
export default class View { _data; render(data) { if (!data || (Array.isArray(data) && data.length === 0)) return this.renderError(); this._data = data; const markup = this._generateMarkup(); this._clear(); this._parentElement.insertAdjacentHTML('afterbegin', markup); } _clear() { this._parentElement.innerHTML = ''; } }
问题原因
- 提前获取DOM元素导致无效:
ResultsView类中的_card = document.querySelector('.card')在类实例化时就执行了,此时页面还未渲染任何卡片,document.querySelector('.card')返回null。后续调用styleCard时,操作的是null元素的样式,自然不会生效。 - 逻辑缺陷:仅给第一个卡片设置颜色:当前控制器中只取了第一个宝可梦的类型颜色,就算DOM操作生效,也只会给一个卡片设置颜色,不符合每个卡片对应自身类型颜色的需求。
- 渲染流程覆盖旧元素:
render方法会先清空父元素再插入新的HTML,就算之前获取到了_card元素,也会被清空移除,后续操作的元素已经不存在于DOM中。
解决方案
最优方案是在生成卡片HTML时直接注入背景色样式,避免后续DOM操作,同时保证每个卡片对应自身的类型颜色:
步骤1:修改resultView.js的_generateMarkupPreview方法
引入typeColor常量,根据当前宝可梦的主类型获取颜色,直接写入卡片的style属性:
import View from './View.js'; import { typeColor } from '../model.js'; // 引入typeColor class ResultsView extends View { _parentElement = document.querySelector('.results'); _generateMarkup() { return this._data.map(this._generateMarkupPreview).join(''); } addHandlerRender(handler) { ['load'].forEach((ev) => window.addEventListener(ev, handler)); } // 移除无用的styleCard方法和_card属性 _generateMarkupPreview(result) { const mainType = result.types[0].type.name; const cardColor = typeColor[mainType]; return ` <li class="preview"> <a class="preview__link preview__link--active" href="#2343"></a> <div class="card" style="background: radial-gradient(circle at 50% 0%, ${cardColor} 36%, #ffffff 36%)"> <p class="hp"><span>HP</span>80</p> <figure class="preview__fig"> <img class="fig" src="${result.image}" /> </figure> <h4 class="txt-description">${result.name[0].toUpperCase() + result.name.slice(1) }</h4> <div class="types"> <span>${result.types[0]?.type.name[0].toUpperCase() + result.types[0].type.name.slice(1) }</span> <span>${result.types[1]?.type.name}</span> </div> <div class="stats"> <div> <h3>60</h3> <p>Attack</p> </div> <div> <h3>60</h3> <p>Defense</p> </div> <div> <h3>60</h3> <p>Speed</p> </div> </div> </div> </li>`; } } export default new ResultsView();
步骤2:简化controller.js代码
移除原有的styleCard调用逻辑,因为现在渲染时已经直接注入样式:
import * as model from './model.js'; import resultView from './views/resultsView.js'; const controlPokemon = async function () { try { await model.loadPokemon(); resultView.render(model.state.pokemon); } catch (err) { console.error(err); } }; const init = function () { resultView.addHandlerRender(controlPokemon); }; init();
这样修改后,每个卡片会根据自身的主类型自动设置对应的背景色,无需后续DOM操作,从根源解决了DOM元素获取无效的问题,同时满足所有卡片的样式需求。
内容的提问来源于stack exchange,提问作者Alazy99
相关产品推荐
相关产品推荐

