如何实现Pokémon库存从首页到详情页的动态同步与更新?
实现Pokémon库存的本地存储与动态更新
核心思路
用localStorage存储每个Pokémon的库存数据,设计如下结构:
// localStorage中key为'pokemonStocks',值为JSON对象 { "bulbasaur": 10, "charmander": 15, ... }
通过Pokémon名称作为key快速读写库存,同时实现数据持久化保存。
步骤1:封装库存读写工具函数
创建utils/storage.js文件(或直接在组件内定义),统一处理localStorage操作:
// 获取指定Pokémon的库存,默认值为10 export const getPokemonStock = (pokemonName) => { const stocks = JSON.parse(localStorage.getItem('pokemonStocks')) || {}; return stocks[pokemonName] || 10; }; // 保存指定Pokémon的库存 export const savePokemonStock = (pokemonName, stock) => { const stocks = JSON.parse(localStorage.getItem('pokemonStocks')) || {}; stocks[pokemonName] = parseInt(stock); localStorage.setItem('pokemonStocks', JSON.stringify(stocks)); };
步骤2:修改首页动态显示库存
替换静态库存为从localStorage读取的实时数据:
import { getPokemonStock } from './utils/storage'; export default function Homepage() { const [pokemons, setPokemons] = useState([]) const [query, setQuery] = useState("") const [search, setSearch] = useState("") let navigate = useNavigate(); // 原有getPokemons、searchPokemon、useEffect逻辑不变 // 监听localStorage变化,实时更新首页库存 useEffect(() => { const handleStorageUpdate = () => { setPokemons([...pokemons]); }; window.addEventListener('storage', handleStorageUpdate); return () => window.removeEventListener('storage', handleStorageUpdate); }, [pokemons]); return ( <div className="app-container"> <h1 className="title">Stok Pokémon</h1> <div className=""> <img src={searchIcon} className="search-icon" /> <input type="text" className="search-box" placeholder="Cari Pokémon" onChange={(e) => setQuery(e.target.value)} onKeyUp={searchPokemon} /> </div> <div className="row"> <div className="col"> <Table className="table d-flex row"> <thead> <tr className="d-flex justify-content-between th-border"> <th scope="col">Nama</th> <th scope="col" className="d-flex text-right">Stok</th> </tr> </thead> <tbody> { pokemons.filter(pokemon => pokemon.name.toLowerCase().includes(query)).map((pokemon, i) => { const stock = getPokemonStock(pokemon.name); return ( <tr className="d-flex justify-content-between" key={i + 1}> <td className="table-link" onClick={() => { navigate(`/pokemon/${pokemon.name}`) }} style={{ textTransform: 'capitalize' }}>{pokemon.name}</td> <td className="pokemon-stock">{stock} pcs</td> </tr> ) }) } </tbody> </Table> </div> </div> </div > ) }
步骤3:修改详情页实现库存更新与展示
调整详情页,动态读取库存并给模态框添加保存逻辑:
import { getPokemonStock, savePokemonStock } from './utils/storage'; function UpdateStockModal(props) { const [input, setInput] = useState({ pcs: 0, lusin: 0 }) const handleChange = e => { let newValue = { ...input, [e.target.name]: e.target.value, }; setInput(newValue); }; // 计算总库存并触发保存 const handleSave = () => { const totalStock = parseInt(input.pcs) + parseInt(12 * input.lusin); props.onSave(totalStock); props.onHide(); }; return ( <Modal {...props} size="lg" aria-labelledby="contained-modal-title-vcenter" className="modal" centered > <Modal.Header closeButton> <Modal.Title className="modal-title"> Update stock </Modal.Title> </Modal.Header> <Modal.Body> <h4>Masukkan jumlah stok yang tersedia di rak saat ini.</h4> <Container> <Row> <Col>Kemasan</Col> <Col>Jumlah</Col> <Col>Stok</Col> </Row> <Row className="modal-table-body"> <Col >Pcs</Col> <Col className="d-flex align-items-center">1 x <Form.Control className="modal-input pcs" type="text" name="pcs" value={input.pcs} onChange={handleChange} /> = </Col> <Col>{input.pcs}</Col> </Row> <Row className="modal-table-body"> <Col>Lusin</Col> <Col className="d-flex align-items-center">12 x <Form.Control name="lusin" className="modal-input lusin" type="text" value={input.lusin} onChange={handleChange} /> = </Col> <Col>{12 * input.lusin}</Col> </Row> <Row className="modal-table-body"> <Col>Total Stok <span>(dalam pcs)</span></Col> <Col>Lusin</Col> <Col>{parseInt(12 * input.lusin) + parseInt(input.pcs)}</Col> </Row> </Container> </Modal.Body> <Modal.Footer> <Button variant="primary" onClick={handleSave} > Simpan </Button> <Button variant="secondary" onClick={props.onHide}> Batal </Button> </Modal.Footer> </Modal> ); } export default function PokemonDetail() { let navigate = useNavigate(); let { name } = useParams() const [modalShow, setModalShow] = React.useState(false); const [currentStock, setCurrentStock] = useState(getPokemonStock(name)); // 保存库存并更新页面显示 const handleSaveStock = (newStock) => { savePokemonStock(name, newStock); setCurrentStock(newStock); // 触发localStorage事件,通知首页更新 window.dispatchEvent(new Event('storage')); }; return ( <div className="pokemon-detail-page"> <div className="pokemon-detail_button-group"> <Button variant="outline-light" className="prev-button" onClick={() => { navigate('/') }}><img src={prevPageIcon}></img>Stok Pokémon</Button> <Button className="update-stock-button" onClick={() => setModalShow(true)}>Update Stok</Button> </div> <p className="pokemon-detail-title" style={{ textTransform: 'capitalize' }}>{name}</p> <div className="pokemon-detail-subtitle"> <p className="pokemon-detail-sub1">Sisa Stok</p> <p className="pokemon-detail-sub2">{currentStock} pcs</p> </div> <div className="pokemon-detail-history"> <p className="pokemon-detail-history1">Riwayat Stok</p> <p className="pokemon-detail-history2">Satuan stok dalam pcs</p> </div> <div> <Table className='col-xs-12 mt-4' responsive> <thead> <tr className="th-border "> <th scope="col">Waktu</th> <th scope="col">Kegiatan</th> <th scope="col">Catatan</th> <th scope="col">Jumlah</th> <th scope="col">Stok</th> </tr> </thead> <tbody> <tr className="align-items-center"> <td className="">2 Apr 2021, 08:00</td> <td className="table-link">Update Stok</td> <td className="">"Stok Awal"</td> <td className="table-count-stock">+10</td> <td className="table-bold">{currentStock}</td> </tr> </tbody> </Table> </div> <UpdateStockModal show={modalShow} onHide={() => setModalShow(false)} onSave={handleSaveStock} /> </div> ) }
额外优化建议
- 输入校验:在模态框中添加数字输入校验,避免非数字值导致计算错误
- 历史记录扩展:可以修改localStorage结构,给每个Pokémon添加库存历史数组,记录每次更新的时间、数量
- 批量初始化:首次加载时,可批量给所有从PokeAPI获取的Pokémon设置默认库存
内容的提问来源于stack exchange,提问作者Flo
相关产品推荐
相关产品推荐

