最終效果圖:
1.功能:各功能元件的組合和渲染
2.點選評價按鈕評論元件出現,可輸入文字和打分,點選取消按鈕關閉評論
3.點選提交按鈕顯示已評價
結構:
1.在src下建立components資料夾,用於存放幾個元件,每個元件建立一個資料夾存放,
2.將檢視展示的App.js移入components資料夾下,且建立App資料夾,
3.每個元件的js檔案均命名為index,為引入方便,
4.OrderItem元件為渲染整個列表,
5.OrderList元件為單個列表內容,
6.Header為頂部紅色部分,
src資料夾index.js所有內容:
1.改變了App.js的位置,引入index.js時的路徑也要改變,
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/index';
ReactDOM.render(<App />, document.getElementById('root'));
複製程式碼
App資料夾下,index.js所有內容:
1.引入OrderList元件和Header元件,引入時無需新增index,index檔案自動預設渲染
2.將兩個元件檢視渲染到該渲染的位置
import React, { Component } from 'react';
import OrderList from '../OrderList'
import Header from '../Header'
import './style.css'
class App extends Component {
render() {
return (
<div className="App">
<Header/>
<OrderList/>
</div>
);
}
}
export default App;複製程式碼
頂部Header中所有內容:
index.js:
import React,{Component} from 'react'
import './style.css'
class index extends Component{
render(){
return(
<header className="header">
我的訂單
</header>
);
}
}
export default index複製程式碼
style.css:
.header{
background-color: #e9203d;
color: #fff;
padding: 15px 10px;
text-align: center;
font-size: 16px;
line-height: 1;
}複製程式碼
單個列表OrderItem中所有內容:
1.建立出單個商品列表介面,
2.判斷評價按鈕的紅色灰色狀態,在state中定義狀態:editing預設為false,
3.在標籤上定義邏輯,如果為true,那麼為已評價,class設定為灰色,如果為false,則保持預設未評價,class設定為紅色,
4.這時建立父元件OrderList,遍歷渲染所有列表,
5.做完父元件的第8項,返回子元件繫結data資料,
6.定義一個const接收父元件props過來data資料的每項資料名,將資料直接繫結在相應位置,
7.建立renderEditArea()函式,為點選評價輸入和評分部分的檢視,
將評價檢視繫結在列表下面,並定義state中editing判定,為true時顯示渲染,為false時不渲染,
{this.state.editing ? this.renderEditArea() : null}複製程式碼
8.給評價按鈕增加onclick事件,handleOpenEidt(),點選時將editing狀態改為true,評價部分渲染顯示,
9.給評價部分文字域textarea繫結onChange讓元件受控,設定文字域value值,在state中初始值設為‘’,然後繫結給textarea的value上,
在handleCommentChange()事件中,把state的value修改為使用者當下輸入,則可監聽到使用者輸入,
10.給文字域的取消按鈕繫結點選事件,在取消按鈕事件中,修改state中的editing為false,comment為‘’,取消後清空文字域文字,並讓評價部分消失,
11. 建立五角星檢視函式,renderStars(),
建立span中間放置繪製的一個五角星,建立陣列1-5遍歷span,在state中定義一個資料為stars初始值為0,遍歷中定義一個linghtClass,判斷當stars大於等於當下的五角星個數時,輸入一個新calss名,用於點亮背景色設定,如果不是則不設定,然後將linghtClass繫結在span的calssName中,
12.給span這個五角星繫結點選事件 onClick={this.handleClickStars.bind(this,item)}
因為要判斷當前點選,所以要給函式繫結item引數,
13.定義五角星點選事件,函式接收一個引數為當前點選的stars,修改state中初始stars的資料為當前點選的資料,
14.給提交按鈕繫結事件,提交時需要提交至父元件所有商品列表中,所以父元件需要接收哪條
商品的id資訊,該商品文字域內容,和打分情況,
將這些所需資訊資料,作為引數定義onSubmit提交事件的引數傳遞給父元件
15.此時去父元件接收
import React, {Component} from 'react'
import './style.css'
class OrderItem extends Component {
constructor(props) {
super(props);
this.state = {
editing: false,
stars: props.data.stars || 0,
comment:props.data.comment || '',
}
}
//商品列表
render() {
const {shop, product, price, picture, ifCommented} = this.props.data;
return (
<div className="OrderItem">
<div className="orderItem_picContainer">
<img className="orderItem_pic" src={picture} alt=""/>
</div>
<div className="orderItem_content">
<div className="orderItem_product">{product}</div>
<div className="orderItem_shop">{shop}</div>
<div className="orderItem_detail">
<div className="orderItem_price">{price}</div>
<div>
{
ifCommented ? (
<button className="orderItem_btn orderItem_btn_grey">
已評價</button>
) : (
<button className="orderItem_btn orderItem_btn_red" onClick={this.handleOpenEidt}>
評價</button>
)
}
</div>
</div>
</div>
{/*當state中editing為true時渲染*/}
{this.state.editing ? this.renderEditArea() : null}
</div>
);
}
//評價部分輸入
renderEditArea() {
return (
<div className="orderItem_commentContainer">
<textarea onChange={this.handleCommentChange} value={this.state.comment} className="orderItem_comment"/>
{this.renderStars()}
<button className="orderItem_btn orderItem_btn_red" onClick={this.handleSubmitComment}>提交</button>
<button className="orderItem_btn orderItem_btn_grey" onClick={this.handleCancelComment}>取消</button>
</div>
)
}
// 五角星打分
renderStars() {
const {stars} = this.state;
return (
<div>
{
[1, 2, 3, 4, 5].map((item, index) => {
const lightClass = stars >= item ?
'orderItem_star_linght' : '';
return (
<span className={"orderItem_star "+ lightClass} key={index} onClick={this.handleClickStars.bind(this,item)}>★</span>
)
})
}
</div>
)
}
//是否出現評價框
handleOpenEidt =()=>{
this.setState({
editing :true
})
}
//讓輸入框受控
handleCommentChange =(e)=>{
this.setState({
comment:e.target.value
})
}
//點亮五角星
handleClickStars =(stars)=>{
this.setState({
stars:stars
})
}
//取消按鈕
handleCancelComment=()=>{
this.setState({
editing:false,
stars: this.props.data.stars || 0,
comment:this.props.data.comment || ''
})
}
//提交按鈕
handleSubmitComment=()=>{
const {id} =this.props.data;
const {comment,stars} = this.state;
this.setState({
editing:false,
})
this.props.onSubmit(id, comment, stars )
}
}
export default OrderItem複製程式碼
父元件所有列表OrderList元件所有程式碼:
1.首先模擬一個從後端獲取的data資料,
在public資料夾中,建立mock資料夾,mock資料夾中建立orders.json檔案用於存放data資料,
3.在鉤子函式componentDidMount()中,用fetch請求獲取地址為‘/mock/orders.json’的資料,
4.在state裡初始化data,
5.在鉤子函式componentDidMount()中將state中初始化的data變為獲取到的data資料,
6.將子元件OrderItem引入,並渲染,
7.將獲取到的data資料遍歷子元件<OrderItem/>,將資料中的id作為唯一的key值,
8.將data資料傳給子元件,用於綁在子元件列表的資料相應位置,此時去子元件,
9.在父元件的子元件標籤中,接收onSubmit事件,同時定義事件函式handleSubmit(),
10.handleSubmit中接收到id,comment,stars三個引數,
定義一個newData,接收改變後的新資料,遍歷舊資料,判斷,當下id和data中的id為同一條時,將所有item接收為子元件的新值和狀態,不是當下這條id,內容不變,
11.將newData更新至data中,
import React, {Component} from 'react'
import OrderItem from '../OrderItem'
class OrderList extends Component {
constructor(props) {
super(props);
this.state = {data: []};
}
//用獲取json資料渲染至列表
componentDidMount() {
//因為public檔案下的資源可以直接被獲取,所以直接寫路徑即可
fetch('/mock/orders.json').then(res => {
if (res.ok) {
res.json().then(data => {
this.setState({
data
})
})
}
})
}
render() {
return (
<div>
{
this.state.data.map(item => {
return <OrderItem key={item.id} data={item} onSubmit={this.handleSubmit}/>
})
}
</div>
);
}
//改變訂單狀態
handleSubmit = (id, comment, stars) => {
//真實專案中,要將評論資訊儲存在伺服器中再操作
fetch('/saveComment').then(()=>{
//以下修改要在這裡執行,但由於現在沒有伺服器
})
const newData = this.state.data.map(item => {
return item.id === id ?
{
...item, comment, stars, ifCommented: true
} : item;
});
//將newData更新至data中
this.setState({
data: newData
})
}
}
export default OrderList複製程式碼
orders.json檔案中所有內容:
1.提取列表檢視中所有動態資料,存放在orders.json檔案中,
用id區分每條資料,
[
{
"id":1,
"shop":"肯德基",
"picture":"http://02.imgmini.eastday.com/mobile/20180405/20180405042401_0e8c94915ba80f288e2aba04de54ce5e_1.jpeg",
"product":"肯德基全家桶",
"price":59.9,
"ifCommented":false
},
{
"id":2,
"shop":"牛排王子",
"picture":"http://www.vx001.com/uploads/allimg/170805/1-1FP510551I62.jpg",
"product":"西冷牛排套餐",
"price":98,
"ifCommented":false
},
{
"id":3,
"shop":"必勝客",
"picture":"http://imgsrc.baidu.com/imgad/pic/item/c995d143ad4bd1131a9ef8c450afa40f4afb05f0.jpg",
"product":"匹薩",
"price":120,
"ifCommented":false
},
{
"id":4,
"shop":"潮人餐廳",
"picture":"http://imgsrc.baidu.com/imgad/pic/item/0d338744ebf81a4c2956133cdd2a6059242da6db.jpg",
"product":"雞排飯",
"price":25,
"ifCommented":false
}
]複製程式碼
OrderItem中的所有css:
.OrderItem{
display: block;
padding:11px 10px 11px 15px;
}
.orderItem_picContainer{
padding-right: 10px;
display: inline-block;
}
.orderItem_pic{
width: 90px;
height: 90px;
}
.orderItem_content{
display: inline-block;
width: calc(100% - 100px);
}
.orderItem_product{
max-width: 237px;
font-size: 17px;
font-weight: 700;
color: #111;
padding-right: 8px;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.orderItem_shop{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
color: #777;
padding-top: 9px;
padding-bottom: 13px;
}
.orderItem_detail{
display: flex;
justify-content: space-between;
height: 22px;
line-height: 22px;
padding-right: 4px;
}
.orderItem_price{
font-size: 13px;
color: #777;
vertical-align: 1px;
margin-left: 10px;
}
.orderItem_price::before{
content: "\A5";
margin-right: 1px;
}
.orderItem_btn{
width: 80px;
height: 22px;
text-align: center;
color: #fff;
border:0;
font-size: 14px;
}
.orderItem_btn_red{
background-color: #e9203d;
}
.orderItem_btn_grey{
background-color: #999;
}
.orderItem_comment{
width: 100%;
resize:none;
margin-top: 10px;
height: 50px;
}
.orderItem_star{
font-size: 25px;
cursor: pointer;
color: #777777;
}
.orderItem_star_linght{
color: #e9203d;
}複製程式碼