本项目采用React脚手架create-react-app搭建,使用less预处理配合rem,使用redux保存调用相关数据. 后端mock数据采用node.js的express框架进行创建.前后端分离,跨域使用http-proxy-middleware中间件. 使用模块化的方式处理各个页面.公共组件抽离到统一目录下方便调用.
React+React-Router(v4)+React-Redux+Less+Express+Fetch
//安装依赖
npm install再另开一个终端
node ./mock/index.js回到第一个终端
npm start- less配置
通过npm install less less-loader -S安装less.
npm eject加载webpack配置,打开config/webpack.config.js文件,
找到sass配置,复制一份.改为less配置.
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;搜索sassRegex和sassModuleRegex将相应的文件配置复制一份进行修改.
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'less-loader'
),
sideEffects: true,
},
{
test: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader'
),
},如果出现
Cannot find module 'resolve'报错,说明依赖没有安装好,大部分是网络原因,运行npm install重新安装一次就好了.
- rem配置
在index.html中的<header>最后位置放入下列代码.
<script>
(function (doc, win) {
var docEl = doc.documentElement,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
recalc = function () {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
if(clientWidth>=750){
docEl.style.fontSize = '100px';
}else{
docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
}
};
if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);
</script>- 创建文件夹
在src下创建以下文件夹.
- 路由文件夹
router - 公共组件文件夹
components - 工具类文件夹
utils - 页面文件夹
pages - 静态文件夹
static
在pages下创建各个文件夹
Home, Shop, Life, Mine, NotFound下生成组件index.jsx和style.less.
在static文件夹下放入初始化资源
css,iconfont,images
- 路由支持
npm install react-router react-router-dom --save
router文件夹下创建AppRouter.jsx文件,将该文件导出挂载到app.js文件内作为组件.
AppRouter文件引入{Route, HashRouter, Switch},pages下各个页面,将路径和component填入各个Route中.
- css文件初始化
在static/common.less中将样式初始化,或者直接引入normalize.css.
- 底部导航
引入iconfont字体图标库.在index.js中也引入.
底部导航是公共组件,在components下创建FootNav文件夹.
引入router的NavLink,因为需要点击跳转.(注意首页的路径使用exact精确匹配)
因为每个页面都会用到底部导航,故在每个页面进行引入.
在各个底部菜单放入图标<i className="">引入各个图标.
- HomeHeader
创建Home/HomeHeader,按照划分分为3部分,home-header-left,right,middle.
其中home-header-middle部分包含一个search-container,之中包含一个iconfont和一个input.
- 轮播图
安装swiper
npm install --save react-swipeable-views在公共组件components文件夹下创建轮播图文件夹Swiper. 引入
import SwipeableViews from 'react-swipeable-views'将图片添加到staic/images文件夹下. 将引入写到home.jsx中,由home向swiper组件传递banner.
<Swiper banners={ [banners1, banners2, banners3] } />Swiper中直接将得到的this.props.banners用map方法遍历到SwipeableVIews中.
在swiper文件夹下创建Pagination文件夹及相关文件.
<Pagination />和<SwipeableViews>是同级关系.
小点的数量由banners的长度决定,在<Pagination dots={banners.length} />中传递给子组件.
此时dots是数字,需要的是数组.使用new Array的方法将长度转化为数组
//转化为[1,1,1]
const dotsArr = new Array(dots).fill(1)将数组map到<li></li>中.
给<SwipeableViews>绑定事件,滑动到的当前的图的下标currentIndex,给赋值到index,传递给<Pagination />,
它得到currentIndex后拿到<li>中进行判断,
<li className={currentIndex === index ? "selected" : ""}>- 搭建服务器
在项目根目录创建mock文件夹(或者server).
包含config,index,router文件和一个data文件夹.
data文件夹下创建home文件夹,热销商品数据hotdata.js.
安装express,node index.js启动.
- 跨域问题
安装npm install http-proxy-middleware --save
创建src/setupProxy.js文件
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function(app) {
app.use(createProxyMiddleware ("/api", { target: "http://localhost:5000/" }));
};- Http处理网络请求
utils文件夹下创建http.js
主要由两个函数构成,getData, postData
import qs from 'querystring'
export function getDate(url){
const result = fetch(url)
return result
}
export ...- 创建API
创建src/api/base.js和index.js,homehot.js.
//base.js
const base = {
baseUrl: "http://localhost:5000",
homehot1: "/api/homehot1",
homehot2: "/api/homehot2"
}// 首页热门推荐
import base from "./base"
import {getData} from "../utils/http"
const homehot = {
homehot1Data(){
return getData(base.homehot1)
},
homehot2Data(){
return getData(base.homehot2)
}
}
export default homehot//index.js
import homehot from './homehot'
//这里注意加括号
export default {homehot}- 创建HomeHot文件夹
引入api.在componentDidMount中调用api请求数据.
componentDidMount(){
api.homehot.homehot1Data()
.then(res => res.json())
.then(data =>{ console.log(data)})
}- 分离HomeHot的UI
创建./HomeHot/HomeHotView.
将data传入HomeHotView,HomeHotView中map遍历数据data,放在li中.
创建style.less样式.
- 城市选项
创建src/pages/City.将相关引入放到router中.
给北京添加<Link>,可以点击跳转到城市页面.
- 城市选择页面
创建公共头部component/Header.
有一个返回箭头,一个title.其中title由使用页面传参
返回箭头绑定返回事件.
window.history.back(-1)
this.props.history.push("/home")
- 当前城市
创建./City/CurrentCity,city也是从父级传递过来.
- 热门城市
创建./City/HotCity.
点击传递参数时,建议使用bind传参,属于隐式传递.使用箭头函数属于显式传递.
此时事件定义时就不能用箭头函数了.
//定义事件
clickHandler(cityName){
console.lig(cityName)
}
//...
//向事件函数传参
//bind隐式传递
<li onClick={this.clickHandler.bind(this,'北京')}>北京</li>
//箭头函数,显式传递
<li onClick={(e)=>this.clickHandler('北京',e)}>北京</li>- 创建Redux
由于每个页面都需要获取城市信息,所以需要使用Redux存储.
安装redux
npm install --save redux react-redux
安装redux调试工具
npm install --save-dev redux-dectools
创建src/store/index.js,src/actions/city.js,src/actions/index.js和src/reducer/index.js.
再创建constants文件用于定义基本字符串.
//src/constants/city.js
export const INIT_CITY = "INIT_CITY"
export const UPDATE_CITY = "UPDATE_CITY"创建初始化城市initCity和更新城市updateCity的函数
//src/actions/city.js
import * as cityActions from "../constants/city";
export function initCity(data) {
return {
type: cityActions.INIT_CITY,
data
};
}
export function updateCity(data) {
return {
type: cityActions.UPDATE_CITY,
data
};
}创建city的Reducer
//src/reducers/city.js
import * as cityActions from '../constants/city'
const initState = {}
export default function city(state = initState, action ){
switch(action.type){
case cityActions.INIT_CITY:
return state = action.data;
case cityActions.UPDATE_CITY:
return state = action.data;
default:
return state;
}
} 合并Reducer
//src/reducers/index.js
import { combineReducers } from "redux"
import city from './city'
const rootReducers = combineReducers({
city
})
export default rootReducers将Reducer传入store中,创建好store.
//src/store/index.js
import { createStore } from 'redux'
import rootReducers from '../reducers'
export default function configureStore(){
const store = createStore(rootReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() :undefined
)
return store
}- 使用Redux
主入口文件index.js进行关联.
//index.js
import {Provider} from 'react-redux'
import configtureStore from './store'
const store = configtureStore();
ReactDOM.render(
<Provider store={store}>
<AppRouter />
</Provider>,
document.getElementById("root")
);创建初始化页面src/pages/app.jsx
在index页面包裹所有的二级路由,并且路径指向首页
初始化项目需求 1.1 城市初始化
引入bindActionCreators,connect
//app.js
import { connect } from 'react-redux'
import * as cityActions from "../actions/city"
import { bindActionCreators } from "redux"
class App extends React.Component{
//初始化城市数据
componentDidMount(){
this.props.cityActions.initCity({
cityName: '北京
})
}
render(){
return(
<div>{this.props.children}</div>
)
}
}
mapStateToProps = state => {
return {
city: state.city
}
}
mapDispatchToProps = dispatch => {
return {
cityActions: bindActionCreators(cityAction, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)1.2 在Home页面引入读取操作
import { connect } from 'react-redux'
const mapStateToProps = state => {
return {
city: state.city
}
}
export default connect(
mapStateToProps
)(Home)1.3 在City页面也要引入
1.4 将HotCity选中的城市传递到主组件City
定义自定义事件进行传递
传递之后,引入mapDispatchToProps触发修改
改变之后,返回上一页面
1.5 传递城市信息
修改mock中参数
//mock/router.js
var url = require("url")
router.get(config.homehot1,function(req,res){
//接收城市作为参数
var cityName = url.parse(req.url,true).query.city
console.log(cityName)
res.send(homehot.hot1)
})Home组件中得到city参数 HomeHot组件中接受到city参数 api中同样接受到参数,注意?符号.
const homehot = {
homehot1Data(city){
return getData(base.homehot1+"?city="+city)city如果被选中后又刷新,需要保存之前的数据.
在City组件中使用localStorage.setItem("city",cityName)
之后再componentDidMount中请求city数据可以默认先查看本地是否有数据
const city = this.props.city || localStorage.getItem("city") || '北京'- 搜索框
搜索框可复用,放在公共组件component中.创建SearchInput组件.绑定相应事件.
创建src/pages/Search页面.在路由中引入
搜索框组件中,绑定按下Enter开始搜索
//src/components/SearchInput
keyUpHandler = (event)=>{
if(event.keyCode === 13){
this.props.history.push('/search')
}
}
...
<input onKeyUp={this.keyUpHandler}/>由于history在组件SearchInput中并没有(只有直接被路由管理的才有),需要从父组件HomeHeader中获取,也没有,继续向上索取, 最后在Home组件中获取. 22. 搜索页面
搜索后跳转到搜索页面,创建搜索页面的顶部搜索框,依旧传入history. 搜索时所填入的数据需要带到搜索页面,用到路由传参. 在路由中
<Route path="/search/:content" component={Search}></Route>数据在SearchInput中,将value附加到push的网址后面传递出去.
this.history.push("/search/"+this.state.value)获取数据,
this.props.match.params.content- 搜索服务器
创建mock/data/search/index.js
在mock/router.js创建搜索接口
// 搜索接口
router.get("/search",function(req,res){
// 接受参数:city,searchcontent
var cityName = url.parse(req.url, true).query.city;
var searchContent = url.parse(req.url, true).query.content;
var page = url.parse(req.url, true).query.page;
console.log("城市:"+cityName,"搜索内容:"+searchContent,"页码:"+page);
res.send(searchData)
})- 添加API
创建api/search
// 搜索接口
import base from "./base"
import {getData} from "../utils/http"
const search = {
searchData(city,content){
return getData(base.search+"?city="+city+"&content="+content)
}
}
export default search/api/index中引入再导出.
- Search页面
创建处理搜索结果的页面SearchList SearchList下创建SearchListView SearchListView下创建Item
//SearchList
componentDidMount(){
//获取城市
const city = this.props.city
//获取搜索内容
const content = this.props.content
api.search.searchData(city,content)
.then(res => res.json())
.then(data => {
this.setState({
searchData: data
})
})
}
render(){
return (
<div>
{
this.state.searchData.data ?
<SearchListView data={this.state.searchData.data}/>
: <div>数据正在加载</div>
}
</div>
)
}SearchListView中将得到的data使用map方法return出Item进行展示. Item将data传入组件内. 组件内将传入的data拆解,渲染.
//Item
const item = this.props.data
return (
<div className="list-item">
<img src={item.img} alt=''/>
<div className="mask">
<div className="left">
<p>{item.title}</p>
<p>{item.houseType}</p>
</div>
<div className="right">
<div className="btn">
{item.rentType}
</div>
<p dangerouslySetInnerHTML={{__html: item.price+"/月"}}/>
</div>
</div>
</div>在生命周期的componentDidUpdate中再次请求
这时组件渲染之后加载
将多次使用的api函数封装成一个函数
http(city,content){
api.search.searchData(city,content)
.then(res => res.json())
.then(data => {
this.setState({
searchData: data
})
})
}将上面的函数在componentDidMount和componentDidUpdate中分别调用.
注意这里容易死循环,组件更新后data被setState重新赋值,然后更新,更新后得到新的参数,又继续更新.死循环.
解决办法: 加上判断,componentDidUpdate有两个参数,prevProps和prevState,表示上一次的属性和state. 比较本次搜索的content和上次是否相等,如果是,就return. 否则,发起网络请求.
componentDidUpdate(prevProps,prevState){
const city = this.props.city
const content = this.props.content
if(prevProps.content === content){
return
}else{
this.http(city, content)
}
}- 搜索详情页的瀑布流
封装上拉加载组件LoadMore
判断是否加载到LoadMore,监听滚动事件.
获取元素偏移量,创建Refs.
constructor(){
super()
this.load = React.crateRef()
}
componentDidMount(){
window.onScroll = () => {
console.log(this.load.current)
}
}
render(){
return(
<div ref={this.load}></div>
)
}滚动事件需要加上函数防抖,只要滚动就不加载触发函数,稍等100ms后加载一次.这就是函数防抖debounce.
//loadMore.js
componentDidMount(){
//事件防抖
let timer = null
const winHeight = document.documentElement.clientHeight
window.onscroll = ()=>{
if(timer){
clearTimeout(timer)
}
timer = setTimeout(()=>{
if(this.load.current.getBoundingClientRect().top < winHeight){
this.props.onLoadMore()
}
},100)
}
}
componentWillUnmount(){
// 取消滚动事件
window.onscroll = null;
}数据加载时是会更新数据,需要把新数据和旧数据拼接在一起.注意要设置初始化的state. 状态上再添加hasMore,判断是否还有数据. 将SearchData全部更改为[],获取时也就只要获取对象里的data.
在SearchList中增加生命周期componentWillUnmount,将请求取消.因为页面如果被点击而跳转到其他页面,请求是异步操作,有可能还没返回. 等回来了数据发现没有state接收请求了,会报错.需要在页面卸载时取消请求.
//searchList.js
http(city,content,page){
api.search.searchData(city,content,page)
.then(res => res.json())
.then(data => {
this.setState({
searchData: this.state.searchData.concat(data.data),
page: this.state.page + 1,
hasMore: data.hasMore
})
})
}
//如果数据请求未完成就跳转到其他页面需要取消网络请求,否则报错
componentWillUnmount(){
// 取消网络请求
this.setState = (state,callback) => {
return;
}
}增加页码,修改服务器. 注意修改api
//api/search.js
const search = {
searchData(city,content,page){
return getData(base.search+"?city="+city+"&content="+content+"&page="+page)
}
}每次做网络请求,page+1. 添加hasMore参数,判断是否还有数据.
- 详情页路由配置
创建pages/Details.路由中增加详情页.
在Item页面进行路由传参,item由Link包裹.<Link to={/detail/${item.id}}>
- 详情页服务端配置
创建mock/data/details/index.js.
在mock/router.js中增加详情接口.
//mock/router.js
//详情接口
router.get("/details", function(req,res){
//接收商品id
var id = url.parse(req.url, true).query.id
console.log(id)
res.send(detailData)
})同时导入detailData数据.
- 详情页API部分
//api/details.js
import base from "./base"
import { getData } from "../utils/http"
const details = {
detailsData(id){
return getData(base.details+"?id="+id);
}
}
export default details;其他文件中增加detail的选项.
- detailData分离
创建page/Details/DetailsData.用来请求数据.
在detail中把id传递进去.
export default class index extends Component {
constructor() {
super();
this.state = {
datas: {}
};
}
componentDidMount() {
const id = this.props.id;
api.detail
.detailsData(id)
.then(res => res.json())
.then(data => {
this.setState({
datas: data
});
});
}
render() {
return (
<div>
{this.state.datas.img ? (
<DetailsDataView data={this.state.datas} />
) : (
<div>数据请求中</div>
)}
</div>
);
}
}创建page/Details/DetailsDataView.用来将请求数据展示出来.
再把请求到的datas传递给DetailsDataView.
DetailsDataView需要多个公共组件,可以直接引入.
- Tab切换
创建公共组件Tabs. 使用React.children.map()去遍历子元素. 绑定onClick事件给点击的绑定高亮.将index赋值给currentIndex.
<div className="Tab_title_wrap">
{
React.Children.map(this.props.children, (element,index)=>{
return(
<div className={this.check_title_index(index)}
onClick={this.tabHandler.bind(this,index)}>
{element.props.tabname}
</div>
)
})
}使用函数赋值给className的显隐切换.
check_title_index = index=>{
return index === this.state.currentIndex ? "Tab_title active" : "Tab_title"
}
check_item_index = index=>{
return index === this.state.currentIndex ? "show" : "hide"
}-
将信息放到Tabs内
-
增加评论服务端数据
创建mock/data/comment.
增加mock/router评论接口.
//评论接口
router.get("/comment", function(req,res){
//获取商品ID
var id = url.parse(req.url, true).query.id
res.send(commentData)
})-
增加api中comment
-
创建详情页DetailData的CommentView组件 将其中的Item拆分出来
-
Star组件
创建公共组件Star. 使用数组判断星星是否高亮.
render(){
if(star >= 5){
star = 5
}
return(
<div className="star-container">
{[1,2,3,4,5].map((item,index)=>{
let lightClass = star >= item ? ' light' : ''
return(
<i key={index} className={'icon-star' + lightClass}></i>
)
})}
</div>
)
}- 登录页面
创建pages/Login页面,储存到Redux.
引入到路由.
创建actions/userinfo.js
创建reducers/userinfo.js,将userinfo写到reducers/index.js中.
创建pages/Login/LoginView页面.展示登录界面.
绑定到Login页面,通过绑定事件loginHandler获取user
- 收藏功能
新增pages/Details/DetailsData/StoreBuy/StoreBuyView.
在DetailsDataView中引入.
收藏页面也增加Redux的连接.点击收藏存在redux中.
创建Redux一套流程.
注意此时actions中的收藏初始数据应为数组.
state存储也是数组形式,
import * as collectActions from "../constants/collect"
const initState = [];
export default function collect(state = initState, action) {
switch (action.type) {
case collectActions.COLLECT:
state.push(action.data)
return state;
case collectActions.UNCOLLECT:
return state.filter((element) => {
//filter作用把当前数组的element遍历,如果和当前所选不等,就是为true
//就把该element添加到新数组里,否则,不添加,这样就排除了所选元素
if (element.id !== action.data.id) {
return element;
}
})
default:
return state;
}
}判断是否收藏.
//StoreBuy
storeHandler = ()=>{
const username = this.props.userinfo.name
const goods_id = this.props.id
if(username){
//调用函数判断
if(this.isStore()){
//为true,说明有,取消收藏
this.props.collectActions.cancelCollect({
id: goods_id
})
}else{
//收藏实现
this.props.collectActions.setCollect({
id: goods_id
})
}
}else{
//去登陆
this.props.history.push("/login")
}
}
//收藏判断
isStore = ()=>{
const id = this.props.id
const collects = this.props.collect
//some判断集合中是否有该id,有返回true
return collects.some((element)=>{
return element.id === id
})
}- 购物车功能
创建pages/ShopCart.
子组件在创建OrderView和UserView
给HomeHeader中购物车图标增加Link标签跳转.
进入组件之前判断登录状态.关联redux.
//ShopCart
componentWillMount(){
const userinfo = this.props.userinfo.name
if(userinfo){
return
}else{
//重定向到登录
this.props.history.push("/login")
}
}- 新增order的服务端数据
创建mock/data/order文件夹.
//order
module.exports = [
{
id: Math.random().toString().slice(2),
title: "东城区 安外大街3号院",
houseType: "1室1厅1卫 - 48m²",
price: "4800",
rentType: "整租",
commentState: 0,
img: "http://iwenwiki.com/api/livable/shop/z1.jpg"
},
{
id: Math.random().toString().slice(2),
title: "整租 · 义宾北区2居室-南北",
houseType: "2室1厅1卫 - 78m²",
price: "7200",
rentType: "整租",
commentState: 0,
img: "http://iwenwiki.com/api/livable/shop/z5.jpg"
},
{
id: Math.random().toString().slice(2),
title: "整租 · 杨庄北区2居室-南北",
houseType: "1室1厅1卫 - 48m²",
price: "4300",
rentType: "整租",
commentState: 2,
img: "http://iwenwiki.com/api/livable/shop/z6.jpg"
}
]mock/router.js中新增order.
// 购物车
router.get("/car",function(req,res){
var user = url.parse(req.url, true).query.user;
console.log("用户:"+user);
res.send(orderComment)
})
router.post("/ordercomment",function(req,res){
var info = req.body.info;
console.log("评论信息:"+info);
res.send({
msg:'评价成功'
})
})- 新增order的api
创建api/order和api/ordercomment.
在base.js中新增order和ordercomment的路由.
在index.js中新增order和ordercomment的引入.
购物车评价接口需要写入数据,所以是postData.
//ordercomment.js
// 订单评价接口
import base from "./base"
import {postData} from "../utils/http"
const orderComment = {
orderCommentData(info){
return postData(base.ordercomment,info)
}
}
export default orderComment- 网络请求
引入api,在登录后发送网络请求
//shopcar
componentWillMount(){
const userinfo = this.props.userinfo.name
if(userinfo){
//网络请求
api.order.orderData(userinfo)
.then(res=>res.json())
.then(data=>{
this.setState({
order: data
})
})
}else{
//重定向到登录
this.props.history.push("/login")
}把数据order传给<OrderView />.
还是先判断数据是否到来
{
this.state.order.length > 0 ?
<OrderView data={this.state.order} />
: <div>数据加载中</div>
}- 数据接收渲染
OrderView接收上级传过来的数据,使用map渲染到页面.
render(){
const data = this.props.order
return(
<div>
{data.map(item, index)=>{
return <Item key={index} data={item} />
}}
</div>
)
} - Item渲染
render() {
const data = this.props.data
return (
<div className="clear-fix order-item-container">
<div className="order-item-img float-left">
<img src={data.img} alt={data.title}/>
</div>
<div className="order-item-comment float-right">
{
data.commentState === 0?
<button className="btn">评价</button>
: <button className="btn unselected-btn">已评价</button>
}
</div>
<div className="order-item-content">
<span>商户:{data.title}</span>
<span>类型:{data.houseType}</span>
<span>价格:{data.price}</span>
</div>
</div>
)
}- 添加评价
给评价绑定事件. 评价状态commentState的值0,1,2分别代表未评价,评价中,已评价.
this.state.commentState === 0 ?
<button className="btn" onClick={this.commentHandler }>评价</button>
: this.state.commentState === 1 ?
<button className="btn">评价中</button>
: <button className="btn unselected-btn">已评价</button>- 添加评价框
{
this.state.commentState === 1 ?
<div className="comment-text-container">
<textarea style={{ width: '100%', height: '80px' }} className="comment-text" ref={ this.commentText }></textarea>
<button className="btn" onClick={this.submitCommentHandler}>提交</button>
<button className="btn unseleted-btn" onClick={this.hideComment}>取消</button>
</div>
: ""
}- 评价事件提交到后台
后台mock/router里创建评价接口.
//mock/router.js
router.post("/ordercomment",function(req,res){
var info = req.body.info;
console.log("评论信息:"+info);
res.send({
msg:'评价成功'
})
})mock/index.js中添加解析.
//mock/index.js
var bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({
extended: true
}))- 增加相关API
创建api/ordercomment
// 订单评价接口
import base from "./base"
import {postData} from "../utils/http"
const orderComment = {
orderCommentData(info){
return postData(base.ordercomment,info)
}
}
export default orderComment- 创建非受控组件
this.commentText = React.createRef()
submitCommentHandler(){
this.setState({
commentState:2
})
// 发送网络请求
api.orderComment.orderCommentData({
info:this.commentText.current.value
})
.then(res => res.json())
.then(data => {
alert(data.msg);
})
}
<textarea ref={this.commentText}></textarea>页面暂时写这么多.