javascript - Javascript:未定义的值
问题描述
我正在构建名为Forkify的示例配方应用程序,其中我使用 javascript、npm、babel、webpack 并使用自定义 API 来获取数据。
API 网址:https ://forkify-api.herokuapp.com/
搜索示例:https ://forkify-api.herokuapp.com/api/search?q=pizza
获取示例:https ://forkify-api.herokuapp.com/api/get?rId=47746
当我运行项目并在登陆屏幕上输入查询以搜索食谱例如: 披萨时,虽然我得到了结果,但必须显示特定食谱成分的部分显示未定义。
下面是项目的代码文件和截图:
index.js
/*
Global state of the app
- search object
- current recipe object
- shopping list object
- liked recipe
*/
import Search from "./models/Search";
import Recipe from "./models/Recipe";
import * as searchView from "./views/searchView";
import * as recipeView from "./views/recipeView";
import { elements, renderLoader, clearLoader } from "./views/base";
const state = {};
/* SEARCH CONTROLLER */
const controlSearch = async () => {
// 1. Get query from the view.
const query = searchView.getInput(); //TODO
if (query) {
// 2. New search object and add it to state.
state.search = new Search(query);
// 3. Prepare UI for results.
searchView.clearInput();
searchView.clearResults();
renderLoader(elements.searchRes);
try {
// 4. Search for recipes.
await state.search.getResults();
// 5. Render results on UI.
clearLoader();
searchView.renderResults(state.search.result);
} catch (error) {
alert("Something wrong with the search...");
clearLoader();
}
}
}
elements.searchForm.addEventListener("submit", e => {
e.preventDefault();
controlSearch();
});
elements.searchResPages.addEventListener("click", e => {
const btn = e.target.closest(".btn-inline");
if (btn) {
const goToPage = parseInt(btn.dataset.goto, 10);
searchView.clearResults();
searchView.renderResults(state.search.result, goToPage);
}
});
/*
RECIPE CONTROLLER
*/
const controlRecipe = async () => {
// Get ID from URL
const id = window.location.hash.replace("#", "");
console.log(id);
if (id) {
// Prepare UI for changes
recipeView.clearRecipe();
renderLoader(elements.recipe); // passing parent
// Create new recipe object
state.recipe = new Recipe(id);
try {
// Get recipe data and parse ingredients
await state.recipe.getRecipe();
state.recipe.parseIngredients();
// Calculate servings and time
state.recipe.calcTime();
console.log(state.recipe.ingredients);
state.recipe.calcServings();
// Render recipe
clearLoader();
recipeView.renderRecipe(state.recipe); // to put recipe
} catch (error) {
// console.log(error);
alert("Error processing recipe !");
}
}
};
["hashchange", "load"].forEach(event => window.addEventListener(event, controlRecipe));
食谱视图.js
import { elements } from "./base";
export const clearRecipe = () => {
elements.recipe.innerHTML = "";
};
const createIngredient = ingredient => `
<li class="recipe__item">
<svg class="recipe__icon">
<use href="img/icons.svg#icon-check"></use>
</svg>
<div class="recipe__count">${ingredient.count}</div>
<div class="recipe__ingredient">
<span class="recipe__unit">${ingredient.unit}</span>
${ingredient.ingredient}
</div>
</li>
`;
export const renderRecipe = recipe => {
const markup = `
<figure class="recipe__fig">
<img src="${recipe.img}" alt="${recipe.title}" class="recipe__img">
<h1 class="recipe__title">
<span>${recipe.title}</span>
</h1>
</figure>
<div class="recipe__details">
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="img/icons.svg#icon-stopwatch"></use>
</svg>
<span class="recipe__info-data recipe__info-data--minutes">${recipe.time}</span>
<span class="recipe__info-text"> minutes</span>
</div>
<div class="recipe__info">
<svg class="recipe__info-icon">
<use href="img/icons.svg#icon-man"></use>
</svg>
<span class="recipe__info-data recipe__info-data--people">${recipe.servings}</span>
<span class="recipe__info-text"> servings</span>
<div class="recipe__info-buttons">
<button class="btn-tiny">
<svg>
<use href="img/icons.svg#icon-circle-with-minus"></use>
</svg>
</button>
<button class="btn-tiny">
<svg>
<use href="img/icons.svg#icon-circle-with-plus"></use>
</svg>
</button>
</div>
</div>
<button class="recipe__love">
<svg class="header__likes">
<use href="img/icons.svg#icon-heart-outlined"></use>
</svg>
</button>
</div>
<div class="recipe__ingredients">
<ul class="recipe__ingredient-list">
${recipe.ingredients.map(el => createIngredient(el)).join("")}
</ul>
<button class="btn-small recipe__btn">
<svg class="search__icon">
<use href="img/icons.svg#icon-shopping-cart"></use>
</svg>
<span>Add to shopping list</span>
</button>
</div>
<div class="recipe__directions">
<h2 class="heading-2">How to cook it</h2>
<p class="recipe__directions-text">
This recipe was carefully designed and tested by
<span class="recipe__by">${recipe.author}</span>. Please check out directions at their website.
</p>
<a class="btn-small recipe__btn" href="${recipe.url}" target="_blank">
<span>Directions</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-right"></use>
</svg>
</a>
</div>
`;
elements.recipe.insertAdjacentHTML("afterbegin", markup);
}
食谱.js
import axios from "axios";
// import {key} from "../config";
export default class Recipe {
constructor(id) {
this.id = id;
}
async getRecipe() {
try {
// const res = await axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
const res = await axios(`https://forkify-api.herokuapp.com/api/get?rId=${this.id}`);
this.title = res.data.recipe.title;
this.author = res.data.recipe.publisher;
this.img = res.data.recipe.image_url;
this.url = res.data.recipe.source_url;
this.ingredients = res.data.recipe.ingredients;
} catch (error) {
console.log(error);
alert("Something went wrong :(");
}
}
calcTime() {
// Assuming that we need 15 minutes for each 3 ingredients
const numIng = this.ingredients.length;
const periods = Math.ceil(numIng / 3);
this.time = periods * 15;
}
calcServings() {
this.servings = 4;
}
parseIngredients() {
const unitsLong = ["tablespoons", "tablespoon", "ounces", "ounce", "teaspoons", "teaspoon", "cups", "pounds"];
const unitsShort = ["tbsp", "tbsp", "oz", "oz", "tsp", "tsp", "cup", "pound"];
const units=[...unitsShort,"kg","g"];
const newIngredients = this.ingredients.map(el => {
// 1. Uniform units
let ingredient = el.toLowerCase();
unitsLong.forEach((unit, i) => {
ingredient = ingredient.replace(unit, unitsShort[i]);
});
// 2. Remove Parenthesis
ingredient = ingredient.replace(/ *\([^)]*\) */g, " ");
// 3. Parse Ingredients into count, unit and ingredients
const arrIng = ingredient.split(" ");
const unitIndex = arrIng.findIndex(el2 => units.includes(el2));
let objIng;
if (unitIndex > -1) {
// there is a unit
// Example 4 1/2 cups, arrCount is [4 , 1/2] --> eval("4+1/2") = 4.5
// Example 4 cups \, arrCount is [4]
const arrCount = arrIng.slice(0, unitIndex);
let count;
if (arrCount.length === 1) {
count = eval(arrIng[0].replace("-", "+"));
}
else {
count = eval(arrIng.slice(0, unitIndex).join("+"));
}
objIng = {
count,
unit: arrIng[unitIndex],
ingredient: arrIng.slice(unitIndex + 1).join(" ")
};
}
else if (parseInt(arrIng[0], 10)) {
// there is no unit but 1st element is number
objIng = {
count: parseInt(arrIng[0], 10),
unit: "",
ingredient: arrIng.slice(1).join(" ")
};
}
else if (unitIndex === -1) {
// there is no unit and no numberin 1st position
objIng = {
count: 1,
unit: "",
ingredient
}
}
return ingredient;
});
this.ingredients = newIngredients;
}
};
搜索视图.js
/*
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export const ID = 23;
*/
import { elements } from "./base";
export const getInput = () => elements.searchInput.value;
export const clearInput = () => {
elements.searchInput.value = "";
};
export const clearResults = () => {
elements.searchResList.innerHTML = "";
elements.searchResPages.innerHTML = "";
};
/* EXAMPLE CODE
"pasta with tomato and spinach"
acc:0/acc+curr.length=5 /newTitle =['pasta']
acc:5/acc+curr.length=9 /newTitle =['pasta','with']
acc:9/acc+curr.length=15 /newTitle =['pasta','with','tomato']
acc:15/acc+curr.length=18 /newTitle =['pasta','with','tomato']
acc:18/acc+curr.length=25 /newTitle =['pasta','with','tomato']
*/
const limitRecipeTitle = (title, limit = 17) => {
const newTitle = [];
if (title.length > limit) {
title.split(" ").reduce((acc, curr) => {
if (acc + curr.length <= limit) {
newTitle.push(curr);
}
return acc + curr.length;
}, 0);
// return the results
return `${newTitle.join(' ')}...`;
}
return title;
};
const renderRecipe = recipe => {
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${recipe.title}">
</figure>
<div class="results__data">
<h4 class="results__name">${limitRecipeTitle(recipe.title)}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
elements.searchResList.insertAdjacentHTML("beforeend", markup);
};
// type: "prev" or "next"
const createButton = (page, type) => `
<button class="btn-inline results__btn--${type}" data-goto=${type === "prev" ? page - 1 : page + 1}>
<span>Page ${ type === "prev" ? page - 1 : page + 1}</span>
<svg class="search__icon">
<use href="img/icons.svg#icon-triangle-${ type === "prev" ? "left" : "right"}"></use>
</svg>
</button>
`
const renderButtons = (page, numResults, resPerPage) => {
const pages = Math.ceil(numResults / resPerPage);
let button;
if (page === 1 && pages > 1) {
// Only button to go to next page.
button = createButton(page, "next");
}
else if (page < pages) {
// Both buttons
button = `
${createButton(page, "prev")}
${createButton(page, "next")}
`;
}
else if (page === pages && pages > 1) {
// Only button to go to previous page.
button = createButton(page, "prev");
}
elements.searchResPages.insertAdjacentHTML("afterbegin", button);
}
export const renderResults = (recipes=[], page = 1, resPerPage = 10) => {
// render results of current page
const start = (page - 1) * resPerPage;
const end = page * resPerPage;
// recipes.slice(start,end).forEach(renderRecipe);
recipes.slice(start,end).forEach(renderRecipe);
// render pagination buttons
renderButtons(page, recipes.length, resPerPage);
};
搜索.js
import axios from "axios";
// import {proxy} from "../config";
export default class Search{
constructor(query){
this.query=query;
}
async getResults() {
try{
const res = await axios(`https://forkify-api.herokuapp.com/api/search?q=${this.query}`);
this.result = res.data.recipes;
// console.log(this.result);
}
catch(error){
alert(error);
}
};
}
base.js
export const elements = {
searchForm: document.querySelector(".search"),
searchInput: document.querySelector(".search__field"),
searchRes: document.querySelector(".results"),
searchResList: document.querySelector(".results__list"),
searchResPages: document.querySelector(".results__pages"),
recipe:document.querySelector(".recipe")
};
export const elementStrings = {
loader: "loader"
};
export const renderLoader = parent => {
const loader = `
<div class="${elementStrings.loader}">
<svg>
<use href="img/icons.svg#icon-cw">
</use>
</svg>
</div>
`;
parent.insertAdjacentHTML("afterbegin", loader);
};
export const clearLoader = () => {
const loader = document.querySelector(`.${elementStrings.loader}`);
if (loader) loader.parentElement.removeChild(loader);
};
截图
查询结果显示为 undefined ,而应显示搜索到的配方的成分
请问有什么解决办法吗?
解决方案
当您尝试将成分解析为一个对象时,您永远不会返回您构建的对象,而是返回一个半解析的字符串。
return ingredient;
应该:
return objIng;
在 Recipe.jsparseIngredients
方法中。
由于您当前的解决方案没有将成分字符串映射到对象,因此您访问的所有属性都将是undefined
(除非是有效的字符串属性)。
// because of this mistake your view is doing
ingredient = "some ingredient"; // <- assume this is passed as a parameter
ingredient.count; //=> undefined
ingredient.unit; //=> undefined
推荐阅读
- javascript - 在 DIV 外部单击时重置评级
- hana - SAP BW\4 HANA 和 SAP 原生 Hana 有什么区别?
- javascript - 尝试将 JavaScript 数组传递给我的 ASP.NET Core 控制器但失败
- ios - 如何快速将任何未定义的变量存储在可编码的结构中
- typescript - 从打字稿界面中提取嵌套字段并将其合并为新类型
- reactjs - 使用方向 rtl 拖动列并调整其大小在 MUI-Datatables 中不起作用
- php - REST API Woocommerce:身份验证
- mongodb - 如何 InsertOne 到 mongo 数据库
- python - Pandas - 是否可以根据其他列中的布尔值创建一个列,将它们视为变量?
- mongodb - Mongodb,获取字段的不同值并将值作为单个文档输出