(十一)项目设计
項(xiàng)目設(shè)計
- 組件和狀態(tài)設(shè)計
- 考察重點(diǎn)
- 面試題
- React實(shí)現(xiàn)Todo List
- state數(shù)據(jù)結(jié)構(gòu)設(shè)計
- 組件設(shè)計(拆分、組合)和組件通訊
- 代碼演示
- 總結(jié)
- Vue實(shí)現(xiàn)購物車
- data數(shù)據(jù)結(jié)構(gòu)設(shè)計
- 組件設(shè)計和組件通訊
- 代碼演示
- 結(jié)合Vuex實(shí)現(xiàn)購物車
- 總結(jié)
組件和狀態(tài)設(shè)計
框架(Vue React)的使用(和高級特性)是必要條件
能獨(dú)立負(fù)責(zé)項(xiàng)目?還是需要別人帶著?—考察設(shè)計能力
面試必考(二面/三面),場景題
考察重點(diǎn)
數(shù)據(jù)驅(qū)動視圖
狀態(tài):數(shù)據(jù)結(jié)構(gòu)設(shè)計(React - state,Vue - data)
視圖:組件結(jié)構(gòu)和拆分
面試題
React設(shè)計todolist(組件結(jié)構(gòu),redux state 數(shù)據(jù)結(jié)構(gòu))
Vue設(shè)計購物車(組件結(jié)構(gòu),vuex state數(shù)據(jù)結(jié)構(gòu))
React實(shí)現(xiàn)Todo List
state數(shù)據(jù)結(jié)構(gòu)設(shè)計
用數(shù)據(jù)描述所有的內(nèi)容
數(shù)據(jù)要結(jié)構(gòu)化,易于程序操作(遍歷、查找)
數(shù)據(jù)要可擴(kuò)展,以便增加新的功能
組件設(shè)計(拆分、組合)和組件通訊
從功能上拆分層次
盡量讓組件原子化(一個組件只負(fù)責(zé)一個功能)
容器組件(只管理數(shù)據(jù))& UI組件(只顯示視圖)
代碼演示
//index.js import React from 'react' import List from './List' import InputItem from './InputItem'class App extends React.Component {constructor(props) {super(props)this.state = {list: [{id: 1,title: '標(biāo)題1',completed: false},{id: 2,title: '標(biāo)題2',completed: false},{id: 3,title: '標(biāo)題3',completed: false}]}}render() {return <div><InputItem addItem={this.addItem}/><Listlist={this.state.list}deleteItem={this.deleteItem}toggleCompleted={this.toggleCompleted}/></div>}// 新增一項(xiàng)addItem = (title) => {const list = this.state.listthis.setState({// 使用 concat 返回不可變值list: list.concat({id: Math.random().toString().slice(-5), // id 累加title,completed: false})})}// 刪除一項(xiàng)deleteItem = (id) => {this.setState({// 使用 filter 返回不可變值list: this.state.list.filter(item => item.id !== id)})}// 切換完成狀態(tài)toggleCompleted = (id) => {this.setState({// 使用 map 返回不可變值list: this.state.list.map(item => {const completed = item.id === id? !item.completed: item.completed // 切換完成狀態(tài)// 返回新對象return {...item,completed}})})} }export default App //List.js import React from 'react' import ListItem from './ListItem'function List({ list = [], deleteItem, toggleCompleted }) {return <div>{list.map(item => <ListItemitem={item}key={item.id}deleteItem={deleteItem}toggleCompleted={toggleCompleted}/>)}</div> }export default List//ListItem.js import React from 'react' import CheckBox from './UI/CheckBox'class ListItem extends React.Component {render() {const { item } = this.propsreturn <div style={{ marginTop: '10px' }}><CheckBox onChange={this.completedChangeHandler}/><span style={{ textDecoration: item.completed ? 'line-through' : 'none' }}>{item.title}</span><button onClick={this.deleteHandler}>刪除</button></div>}completedChangeHandler = (checked) => {console.log('checked', checked)const { item, toggleCompleted } = this.propstoggleCompleted(item.id)}deleteHandler = () => {const { item, deleteItem } = this.propsdeleteItem(item.id)} }export default ListItem//CheckBox.js import React from 'react'class CheckBox extends React.Component {constructor(props) {super(props)this.state = {checked: false}}render() {return <input type="checkbox" checked={this.state.checked} onChange={this.onCheckboxChange}/>}onCheckboxChange = () => {const newVal = !this.state.checkedthis.setState({checked: newVal})// 傳給父組件this.props.onChange(newVal)} }export default CheckBox //InputItem.js import React from 'react' import Input from './UI/Input'class InputItem extends React.Component {constructor(props) {super(props)this.state = {title: ''}}render() {return <div><Input value={this.state.title} onChange={this.changeHandler}/><button onClick={this.clickHandler}>新增</button></div>}changeHandler = (newTitle) => {this.setState({title: newTitle})}clickHandler = () => {const { addItem } = this.propsaddItem(this.state.title)this.setState({title: ''})} }export default InputItem //Input.js import React from 'react'class Input extends React.Component {render() {return <input value={this.props.value} onChange={this.onChange}/>}onChange = (e) => {// 傳給父組件const newVal = e.target.valuethis.props.onChange(newVal)} }export default Input總結(jié)
state數(shù)據(jù)結(jié)構(gòu)設(shè)計
組件設(shè)計組件通訊
結(jié)合redux
Vue實(shí)現(xiàn)購物車
data數(shù)據(jù)結(jié)構(gòu)設(shè)計
用數(shù)據(jù)描述所有的內(nèi)容
數(shù)據(jù)要結(jié)構(gòu)化,易于程序操作(遍歷、查找)
數(shù)據(jù)要可擴(kuò)展,以便增加新的功能
組件設(shè)計和組件通訊
從功能上拆分層次
盡量讓組件原子化
容器組件(只管理數(shù)據(jù))& UI組件(只顯示視圖)
代碼演示
//index.vue <template><div><ProductionList :list="productionList"/><hr><CartList:productionList="productionList":cartList="cartList"/></div> </template><script> import ProductionList from './ProductionList/index' import CartList from './CartList/index' import event from './event'export default {components: {ProductionList,CartList},data() {return {productionList: [{id: 1,title: '商品A',price: 10},{id: 2,title: '商品B',price: 15},{id: 3,title: '商品C',price: 20}],cartList: [{id: 1,quantity: 1 // 購物數(shù)量}]}},methods: {// 加入購物車addToCart(id) {// 先看購物車中是否有該商品const prd = this.cartList.find(item => item.id === id)if (prd) {// 數(shù)量加一prd.quantity++return}// 購物車沒有該商品this.cartList.push({id,quantity: 1 // 默認(rèn)購物數(shù)量 1})},// 從購物車刪除一個(即購物數(shù)量減一)delFromCart(id) {// 從購物車中找出該商品const prd = this.cartList.find(item => item.id === id)if (prd == null) {return}// 數(shù)量減一prd.quantity--// 如果數(shù)量減少到了 0if (prd.quantity <= 0) {this.cartList = this.cartList.filter(item => item.id !== id)}}},mounted() {event.$on('addToCart', this.addToCart)event.$on('delFromCart', this.delFromCart)} } </script> // ProductionList <template><div><ProductionItemv-for="item in list":key="item.id":item="item"/></div> </template><script> import ProductionItem from './ProductionItem'export default {components: {ProductionItem,},props: {list: {type: Array,default() {return [// {// id: 1,// title: '商品A',// price: 10// }]}}} } </script> //ProductionItem.vue <template><div><span>{{item.title}}</span> <span>{{item.price}}元</span> <a href="#" @click="clickHandler(item.id, $event)">加入購物車</a></div> </template><script> import event from '../event'export default {props: {item: {type: Object,default() {return {// id: 1,// title: '商品A',// price: 10}}}},methods: {clickHandler(id, e) {e.preventDefault()event.$emit('addToCart', id)}}, } </script> //CartList <template><div><CartItemv-for="item in list":key="item.id":item="item"/><p>總價 {{totalPrice}}</p></div> </template><script> import CartItem from './CartItem'export default {components: {CartItem,},props: {productionList: {type: Array,default() {return [// {// id: 1,// title: '商品A',// price: 10// }]}},cartList: {type: Array,default() {return [// {// id: 1,// quantity: 1// }]}}},computed: {// 購物車商品列表list() {return this.cartList.map(cartListItem => {// 找到對應(yīng)的 productionItemconst productionItem = this.productionList.find(prdItem => prdItem.id === cartListItem.id)// 返回商品信息,外加購物數(shù)量return {...productionItem,quantity: cartListItem.quantity}// 如:// {// id: 1,// title: '商品A',// price: 10,// quantity: 1 // 購物數(shù)量// }})},// 總價totalPrice() {return this.list.reduce((total, curItem) => total + (curItem.quantity * curItem.price),0)}} } </script> //CartItem.vue <template><div><span>{{item.title}}</span> <span>(數(shù)量 {{item.quantity}})</span> <a href="#" @click="addClickHandler(item.id, $event)">增加</a> <a href="#" @click="delClickHandler(item.id, $event)">減少</a></div> </template><script> import event from '../event'export default {props: {item: {type: Object,default() {return {// id: 1,// title: '商品A',// price: 10,// quantity: 1 // 購物數(shù)量}}}},methods: {addClickHandler(id, e) {e.preventDefault()event.$emit('addToCart', id)},delClickHandler(id, e) {e.preventDefault()event.$emit('delFromCart', id)}} } </script> //TotalPrice.vue <template><p>total price</p> </template><script> export default {data() {return {}} } </script>結(jié)合Vuex實(shí)現(xiàn)購物車
//App.vue <template><div id="app"><h1>Shopping Cart Example</h1><hr><h2>Products</h2><ProductList/><hr><ShoppingCart/></div> </template><script> import ProductList from './ProductList.vue' import ShoppingCart from './ShoppingCart.vue'export default {components: { ProductList, ShoppingCart } } </script>//ProductList.vue <template><ul><liv-for="product in products":key="product.id">{{ product.title }} - {{ product.price | currency }}(inventory: {{product.inventory}})<!-- 這里可以自己加一下顯示庫存 --><br><button:disabled="!product.inventory"@click="addProductToCart(product)">Add to cart</button></li></ul> </template><script> import { mapState, mapActions } from 'vuex'export default {computed: mapState({// 獲取所有商品products: state => state.products.all}),methods: mapActions('cart', [// 添加商品到購物車'addProductToCart']),created () {// 加載所有商品this.$store.dispatch('products/getAllProducts')} } </script> //ShoppingCart.vue <template><div class="cart"><h2>Your Cart</h2><p v-show="!products.length"><i>Please add some products to cart.</i></p><ul><liv-for="product in products":key="product.id">{{ product.title }} - {{ product.price | currency }} x {{ product.quantity }}</li></ul><p>Total: {{ total | currency }}</p><p><button :disabled="!products.length" @click="checkout(products)">Checkout</button></p><p v-show="checkoutStatus">Checkout {{ checkoutStatus }}.</p></div> </template><script> import { mapGetters, mapState } from 'vuex'export default {computed: {...mapState({// 結(jié)賬的狀態(tài)checkoutStatus: state => state.cart.checkoutStatus}),...mapGetters('cart', {products: 'cartProducts', // 購物車的商品total: 'cartTotalPrice' // 購物車商品的總價格})},methods: {// 結(jié)賬checkout (products) {this.$store.dispatch('cart/checkout', products)}} } </script> //store //index.js import Vue from 'vue' import Vuex from 'vuex' import cart from './modules/cart' import products from './modules/products' import createLogger from '../../../src/plugins/logger'Vue.use(Vuex)const debug = process.env.NODE_ENV !== 'production'export default new Vuex.Store({modules: {cart,products},strict: debug,plugins: debug ? [createLogger()] : [] })//mock shop.js /*** Mocking client-server processing*/ const _products = [{"id": 1, "title": "iPad 4 Mini", "price": 500.01, "inventory": 2},{"id": 2, "title": "H&M T-Shirt White", "price": 10.99, "inventory": 10},{"id": 3, "title": "Charli XCX - Sucker CD", "price": 19.99, "inventory": 5} ]export default {// 獲取所有商品,異步模擬 ajaxgetProducts (cb) {setTimeout(() => cb(_products), 100)},// 結(jié)賬,異步模擬 ajaxbuyProducts (products, cb, errorCb) {setTimeout(() => {// simulate random checkout failure.// 模擬可能失敗的情況(Math.random() > 0.5 || navigator.userAgent.indexOf('PhantomJS') > -1)? cb(): errorCb()}, 100)} }//cart.js import shop from '../../api/shop'// initial state // shape: [{ id, quantity }] const state = {// 已加入購物車的商品,格式如 [{ id, quantity }, { id, quantity }]// 注意,購物車只存儲 id 和數(shù)量,其他商品信息不存儲items: [],// 結(jié)賬的狀態(tài) - null successful failedcheckoutStatus: null }// getters const getters = {// 獲取購物車商品cartProducts: (state, getters, rootState) => {// rootState - 全局 state// 購物車 items 只有 id quantity ,沒有其他商品信息。要從這里獲取。return state.items.map(({ id, quantity }) => {// 從商品列表中,根據(jù) id 獲取商品信息const product = rootState.products.all.find(product => product.id === id)return {title: product.title,price: product.price,quantity}})},// 所有購物車商品的價格總和cartTotalPrice: (state, getters) => {// reduce 的經(jīng)典使用場景,求和return getters.cartProducts.reduce((total, product) => {return total + product.price * product.quantity}, 0)} }// actions —— 異步操作要放在 actions const actions = {// 結(jié)算checkout ({ commit, state }, products) {// 獲取購物車的商品const savedCartItems = [...state.items]// 設(shè)置結(jié)賬的狀態(tài) nullcommit('setCheckoutStatus', null)// empty cart 清空購物車commit('setCartItems', { items: [] })// 請求接口shop.buyProducts(products,() => commit('setCheckoutStatus', 'successful'), // 設(shè)置結(jié)賬的狀態(tài) successful() => {commit('setCheckoutStatus', 'failed') // 設(shè)置結(jié)賬的狀態(tài) failed// rollback to the cart saved before sending the request// 失敗了,就要重新還原購物車的數(shù)據(jù)commit('setCartItems', { items: savedCartItems })})},// 添加到購物車// 【注意】這里沒有異步,為何要用 actions ???—— 因?yàn)橐隙鄠€ mutation// mutation 是原子,其中不可再進(jìn)行 commit !!!addProductToCart ({ state, commit }, product) {commit('setCheckoutStatus', null) // 設(shè)置結(jié)賬的狀態(tài) null// 判斷庫存是否足夠if (product.inventory > 0) {const cartItem = state.items.find(item => item.id === product.id)if (!cartItem) {// 初次添加到購物車commit('pushProductToCart', { id: product.id })} else {// 再次添加購物車,增加數(shù)量即可commit('incrementItemQuantity', cartItem)}// remove 1 item from stock 減少庫存commit('products/decrementProductInventory', { id: product.id }, { root: true })}} }// mutations const mutations = {// 商品初次添加到購物車pushProductToCart (state, { id }) {state.items.push({id,quantity: 1})},// 商品再次被添加到購物車,增加商品數(shù)量incrementItemQuantity (state, { id }) {const cartItem = state.items.find(item => item.id === id)cartItem.quantity++},// 設(shè)置購物車數(shù)據(jù)setCartItems (state, { items }) {state.items = items},// 設(shè)置結(jié)算狀態(tài)setCheckoutStatus (state, status) {state.checkoutStatus = status} }export default {namespaced: true,state,getters,actions,mutations }//products.js import shop from '../../api/shop'// initial state const state = {all: [] }// getters const getters = {}// actions —— 異步操作要放在 actions const actions = {// 加載所有商品getAllProducts ({ commit }) {// 從 shop API 加載所有商品,模擬異步shop.getProducts(products => {commit('setProducts', products)})} }// mutations const mutations = {// 設(shè)置所有商品setProducts (state, products) {state.all = products},// 減少某一個商品的庫存(夠買一個,庫存就相應(yīng)的減少一個,合理)decrementProductInventory (state, { id }) {const product = state.all.find(product => product.id === id)product.inventory--} }export default {namespaced: true,state,getters,actions,mutations }總結(jié)
data數(shù)據(jù)結(jié)構(gòu)設(shè)計
組件設(shè)計組件通訊
結(jié)合redux
總結(jié)
- 上一篇: 酷狗大字版怎么填写邀请码(酷狗音乐软件官
- 下一篇: (十二)项目流程