調用后返回上一頁&&跳轉至當前頁
this.props.history.goBack(); 返回
this.props.history.push("/index/setting/basicsetting"); 跳轉
this.props.history.go(-1) 跳轉
當使用時出現以下錯誤Cannot read property 'push' of undefined,
因為父組件調用子組件定義的跳轉事件時,要傳遞history,這里history未定義
(比如app.js這個組件,一般是首頁,不是通過路由跳轉過來的,而是直接從瀏覽器中輸入地址打開的,如果不使用withRouter此組件的this.props為空,沒法執行props中的history、location、match等方法)
解決方法:
import React from "react";
import {withRouter} from "react-router-dom"; //第一,引入withRouter
class MyComponent extends React.Component {
...
myFunction() {
this.props.history.push("/some/Path");
}
...
}
export default withRouter(MyComponent); //第二,不要在上面暴露模塊,在這里使用withRouter暴露模塊
詳細可見:react-router v4 使用 history 控制路由跳轉 https://github.com/brickspert/blog/issues/3
調用后重新渲染當前頁&&跳轉至當前頁
this.props.history.go(0) 跳轉
state={
visible:false
}
handleClick=()=>{
this.setState({
visible:true
});
}
this.setState(this.state) //此語句執行過后頁面重新渲染state。這里需要注意的事重新渲染會將visible渲染成state里的false,而不是handleClick里的true
//渲染會把state里的內容全部重新渲染,即執行this.setState(state)之后,visible會被重新渲染為false,若需要visible為true,可以在渲染之后重新定義狀態,如下重新定義
this.setState({this.state})
this.setState({
visible:true
});
定義componentWillMount=async()=>{
this.getData(); //重新渲染當前頁面
} // 使用:調用this.getData();函數即可
async getData(){
代碼段
}
代碼解析
let {state}=this; // 即 let state = this.state;
let res = await api.SettingGetInfo(this.props.match.params) //獲取列表里當前元素的id值,此方法需要注意有一個必要條件在route頁面里的route添加 /:id
//即<Route path="/index/setting/basicsetting/:id" component={BasicSettting} />
rowKey={record=>record.id} //表格table中的每個記錄應該有唯一的“key”支持,或者將“rowKey”設置為唯一的主鍵,否則會報錯。這里是給表格設置rowKey
點擊按鈕復制相關內容(這里以復制鏈接為例)
npm i --save copy-to-clipboard //首先安裝相關包
import copy from 'copy-to-clipboard'; //引入模塊
render:(a, code, index)=>{
return <span>
<a href="# " onClick={()=>{this.copyLink(code)}}>復制鏈接</a>
</span>
}
copyLink=(code)=>{
copy(`https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${code.info}`)
message.success("鏈接復制成功",2.5)
}
三目運算符的嵌套使用
正常使用:code.type===1?(adate<bdate?atime:btime):<span className="text-success">永不</span>
react里:{title:"時間",dataIndex:"Time",render(a,code,index){
let adate=...;
let bdate=...;
var overtime=<span className="text-danger">時間不足</span>;
var forevertime=<span className="text-success">永久</span>
var times=`${date.getFullYear()}-${date.getMonth() + 1<10?"0"+(date.getMonth()+1):date.getMonth()+1}-${date.getDate()<10?"0"+date.getDate():date.getDate()} ${date.getHours()<10?"0"+date.getHours():date.getHours()}:${date.getMinutes()<10?"0"+date.getMinutes():date.getMinutes()}:${date.getSeconds()<10?"0"+date.getSeconds():date.getSeconds()}` //將時間戳轉換為日期格式
return <span>{code.type===1?(adate<bdate?overtime:times):forevertime}</span> //嵌套使用
}}
======================================================================================================================================================
接口部分
讀懂接口文檔
/api/wechat/code/:officialId/:qrcodeId 帶冒號,動態數據:`${base}/api/wechat/code/${localStorage.getItem("listid")}/${params.id}` //這列的:qrcodeId需要使用${params.id}動態寫入,params即數組
/api/wechat/statistics/:officialId/code 不帶冒號,靜態的:`${base}/api/wechat/statistics/${localStorage.getItem("listid")}/code` //這里的qrcode直接寫上去就可以
參數問題
request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>; //request 一個參數 config
get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; //get 兩個參數 url config
delete(url: string, config?: AxiosRequestConfig): AxiosPromise; //delete 兩個參數 url config
head(url: string, config?: AxiosRequestConfig): AxiosPromise; //head 兩個參數 url config
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; //post 三個參數 url data config
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; // put 三個參數 url data config
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; // patch 三個參數 url data config
參數解釋 :url —— 請求地址
data——發送給服務器的請求數據
config——配置,一般都是固定格式
舉個栗子:
export default{
async codeDesign(params){
return await axios.put(`${base}/api/code/${localStorage.getItem("listid")}/${params.id}`, params,{ //url data
headers:{ //config 一般都是固定格式
"token":cookie.load("usertoken")
}
}).then((res)=>{
return res.data;
});
},
async codeExtend(params){
return axios.post(`${base}/api/code/${localStorage.getItem('listid')}/${params.id}/extend`,{},{ //這里data就沒有傳參,而是用{}表示
headers:{
"token":cookie.load("usertoken")
}
}).then((res)=>{
return res.data;
});
},
async codeGetMsg(params){
return await axios.get(`${base}/api/code/${localStorage.getItem("listid")}/${params.id}`,{
params, **//如果是get delete兩個參數的這種,是沒有data參數的,那么params不能放在data里,就可以放在config里**
headers:{
"token":cookie.load("usertoken")
}
}).then((res)=>{
return res.data;
});
},
}
注意:需要的參數,一個都不能少,data如果不需要傳,設置為{}都行,也不能不傳
axios實戰舉例
state={
//上傳接口的參數
title:"",
author:"",
digest:"",
content:"",
piclist:[], //從服務器獲取的圖片數組
picTotal:2,
filter:{
isloading:false,
type:"image",
flag:2,
page:1,
pageSize:18,
},
}
//上傳pictextData數據到服務器
addPictext=async()=>{
let {title,author,digest,content}=this.state.pictextData;
if(!title){
this.setState({
pictextTitleRemind:true
})
return;
}else{
this.setState({
pictextTitleRemind:false
})
}
if(!(/^(((https?):\/\/|\w+(\.\w+)+)(:\w+)?).*/.test(contentSourceUrl))){ //匹配正則
this.setState({
urlRemind:true
})
return;
}else{
this.setState({
urlRemind:false
})
}
let data=await api.pictextAdd({
title,
author,
digest,
content,
})
if(data.code!==0){
console.log(api.stateCode[data.code]);
return;
}
if(data.code===0){
message.success("保存成功!",3);
this.props.history.push("/index/setting/pictextIndex");
}
}
componentWillMount=async()=>{ //render加載之前就獲取到圖片信息
this.getData();
}
//獲取圖片的接口
async getData(){
this.setState({
filter: Object.assign(this.state.filter, {
isloading: true
}),
});
let res=await api.picGetList(this.state.filter)
this.setState({
filter: Object.assign(this.state.filter, {
isloading: false
}),
if(res.code===0){
this.state.picTotal=res.data.total;
this.state.piclist=res.data.data;
}else{
console.log(api.stateCode[res.code]);
}
this.setState(this.state); //重新渲染頁面
}
//分頁
onChangePage = (page = 1, pageSize = 18) => {
this.setState(
Object.assign(this.state.filter, {
page,pageSize
})
);
this.getData(); //重新加載getData函數,為了重新渲染頁面
}
//刪除圖片接口
deletePic=async(item)=>{
this.setState({
imgDelete:false,
picImgDelete:false,
})
let data=await api.picDelete({
id:item.id, //獲取需要刪除的圖片的id
})
if(data.code!==0){
console.log(api.stateCode[data.code]);
return;
}else if(data.code===0){
this.onChangePage();
message.success(`刪除成功!`,2.5);
}
this.getData();
}
<Pagination hideOnSinglePage onChange={this.onChangePage} current={this.state.filter.page} pageSize={this.state.filter.pageSize} total={this.state.picTotal} /> //antd分頁頁標
受控組件和不受控組件
在HTML中,<textarea> 的值是通過子屬性設置的。在React中,需要通過value設置。我們可以通過添加事件屬性onChange監聽內容的變化,onChange會在下列情況下被觸發:
input或者textarea的內容改變
input的checked狀態改變
select的狀態改變
【受控組件】
設定了value的input就是一個受控組件。input里會一直展現這個值,用戶的任何輸入都是無效的。如果你想隨着用戶的輸入改變,使用onChange事件,或者將value改為defaultValue
【非受控組件】
value沒有值或者值設為null的input是一個不受控組件。用戶的任何輸入都會反映到輸入框中
這個時候也可以監聽onChange事件,內容的改變也會觸發事件。
可以通過defaultValue給input設置默認值
指定編輯(如刪除,更新等)
有一種需求是點擊table中當前tr的“編輯”按鈕后,跳轉到新的編輯頁面編輯當前tr的信息。
這種情況是需要動態獲取id的
方法如下:
1、在route.js即路由頁面將編輯頁面的路由給一個/:id,如下
<Route path="/index/setting/code/code-edit/:id" component={codeEdit} /> //注意是/:id /別忘了
2、列表頁面編輯按鈕的Link跳轉給一個動態id(該id由后端接口數據里給出),如下
<Link to={`/index/setting/code/code-edit/${codelist.id}?id=${codelist.id}`} className="mr-10px">編輯</Link>
3、在編輯頁面對接接口時,參數應使用this.props.match.params
async getPictextData(){
let res =await api.pictextGetEdit(this.props.match.params) //這里傳入的是this.props.match.params
if(res.code===0){
state.pictextEditData=res.data;
}else{
console.log(api.stateCode[res.code]);
}
this.setState(state)
}
提個醒:this.props.match.params是從另一個頁面通過路由帶到當前頁面的數據。你可以在任何地方使用它。
這也表明了,這個傳遞的數據,不一定得是id,也可以是其他的數據。比如name,size等,通過/:name就可以傳遞
就比如說console.log(this.props.match.params)打印出來的是{"id":"1"}。你就可以通過this.props.match.params.id來獲取到1。
但有一點,這個1可能是字符串類型的。和number類型的不是一個數據類型。所以在做判斷是否相等的時候要注意,用==而不用===
在控制台打印出的數字,黑色的就是string類型。藍色的是number類型
使用搜索框搜索數據(后端接口沒有設置好,全部由前端操作)
state={
filter: {
name:"",
total: 0,
page:1,
pageSize:20,
}
}
handleProductSearch=(e)=>{
this.setState(
Object.assign(this.state.filter,{
name:e.target.value, //在對接接口之后。若想根據名稱name搜索,將搜索框的event賦值給filter里的name便可
})
);
this.setState(state); //重新渲染頁面
}
<Search
placeholder="請輸入產品名稱"
onSearch={this.handleProductSearch}
/>
使用搜索框搜索數據,(后端接口已經設置好的情況下)
state={
filter: {
name:"",
total: 0,
page:1,
pageSize:20,
}
}
productName=(e)=>{
this.setState(
Object.assign(this.state.filter,{
name:e.target.value, //在對接接口之后。若想根據名稱name搜索,將搜索框的值賦值給filter里的name便可
})
);
}
handleProductSearch=(e)=>{
this.productInfoGet(); //調用獲取產品信息的接口。此時的name已經是搜索的name。給接口接口會返回搜索后的結果
}
<Search
placeholder="請輸入產品名稱"
onChange={this.productName}
onSearch={this.handleProductSearch}
/>
======================================================================================================================================================
localStorage解釋
localStorage.setItem("key","value");//以“key”為名稱存儲一個值“value”
localStorage.getItem("key");//獲取名稱為“key”的值
localStorage.removeItem("key");//刪除名稱為“key”的信息。
localStorage.clear();//清空localStorage中所有信息
state里的filter里得數據怎么setState?(使用Object.assign相關知識)
this.state={
visible:false,
filter: {
isloading: false,
name:"",
page:1,
pageSize:20,
}
}
這里的isloading怎么設置為true呢?
handleClick=()=>{
this.setState({
filter:Object.assign(this.state.filter,{
isloading:true
}),
});
}
動態添加css樣式(如通過點擊添加css樣式)
state={
select: "",
}
handleSelect=(item)=>{
this.setState({
select: item.id
})
}
<div onClick={()=>{this.handleSelect(item)}} style={{border:this.state.select===item.id?"1px solid #28a745":null}}></div>
動態添加一組可編輯框(如點擊添加按鈕后會添加一個可輸入數值的圖文框)
思路:
1、state一個數組如Data,將數組的值與圖文框綁定起來,通過map函數渲染到頁面
2、當點擊添加按鈕后,執行函數,往list數組里push一組數據,數據會自動渲染到頁面,即實現了動態添加圖文框的功能
3、通過編輯數組的值即可動態編輯圖文框里的內容
4、動態刪除可以通過用數組的splice操作數組的下標index來實現刪除。
總結:通過操作數組的數據來實現圖文框的增刪改,而不是通過dom操作
舉個例子:
state={
title:"", //上傳接口的參數
author:"",
digest:"",
content:"",
Data:[{id:"1",title:"",author:"",digest:"",content:""}],
boxid:"1", //定義圖文框的id
msg:[],
changeBorder:"",
}
//添加圖文框數組
addBox=()=>{
this.setState({
boxid:this.state.boxid++,
})
this.state.Data.push({id:`${this.state.boxid}`,title:"",author:"",digest:"",content:""})
this.setState(this.state)
}
//刪除圖文框
listbox_Delete=(item)=>{
let index= this.state.Data.indexOf(item) //獲取需要刪除的圖文框的index
this.state.Data.splice(index,1) //使用splice方法刪除數組數據
}
//向上移動圖文框(通過改變替換index來實現)
listbox_up=(item)=>{
let index=this.state.Data.indexOf(item)
let arr=this.state.Data
let index2=index-1
if(index2!==0){
arr[index]=arr.splice(index2,1,arr[index])[0]
}
}
//向下移動圖文框
listbox_down=(item)=>{
let index=this.state.Data.indexOf(item)
let arr=this.state.Data
let index2=index+1
if(index2!==arr.length){
arr[index]=arr.splice(index2,1,arr[index])[0]
}
}
//圖文框數組map方式渲染到頁面
{
this.state.Data.map((item,index)=>{
if(item.id==1){ //item.id==1時是主圖文框的樣式,否則是副圖文框的樣式
return <div onClick={()=>{this.handleSelect(item)}} key={index}> //map語法必須要帶有key={index},不然會報錯
<div >
<img src={item.picUrl} alt="" style={{width:"100%",height:"100%"}}/>
</div>
{item.title?<div className="indexbox_mask">{item.title}</div>:null}
</div>
}else{
return <div key={index} style={{border:this.state.changeBorder===item.id?"2px solid #28a745":null}} onClick={()=>{this.handleSelect(item)}}> //handleSelect,點擊后選取當前框並與輸入框綁定
<h4 style={{height:21}}>{item.title}</h4>
<div className="listbox_content">
<div className="listbox_img">
<img src={item.picUrl} alt=""/>
</div>
</div>
<div className="listbox_mask">
<Icon type="up"className="listbox_icon mr-10px" onClick={()=>{this.listbox_up(item)}} /> //圖文框位置上移
<Icon type="down" className="listbox_icon" onClick={()=>{this.listbox_down(item)}} /> //圖文框位置下移
<div className="float-r">
<Icon type="delete"className="listbox_icon" onClick={()=>{this.listbox_Delete(item)}}/> //刪除圖文框
</div>
</div>
</div>
}
})
}
<Button onClick={this.addBox}></Button> //添加圖文框
//點擊圖文框后選定當前圖文框,並與Input輸入框綁定,之后在Input里輸入的值都會存在當前數組里。再點擊其他圖文框后執行同樣的操作,最后所有圖文框里采集的數據會全部存在Data數組里,通過接口將數組傳入后台,再在另一個頁面獲取並渲染該組數據,即完成了前后端的一個交接流程。
handleSelect=(item)=>{
console.log(item)
this.setState({
msg:item,
changeBorder:item.id,
title:item.title,
author:item.author,
digest:item.digest,
content:item.content,
})
}
handleGetTitle=(e)=>{ //與Input標簽之間進行雙向綁定
this.setState({
title:e.target.value,
})
this.state.msg.title=e.target.value; //使得輸入框與當前選定的文本框綁定
}
<Input onChange={this.handleGetTitle} id={this.state.msg.id} value={this.state.title} placeholder="請輸入標題"/>
antd圖片上傳
//判斷用戶上傳文件的格式,不符合提醒用戶,符合則返回true
function beforeUpload(file) {
if(file.type!=='image/png'&&file.type!=='image/jpeg'&&file.type!=='image/jpg'&&file.type!=='image/gif'){
message.error("上傳失敗!僅支持jpeg,png,jpg,gif圖片格式");
return false;
}else if(file.size/1024/1024>4){
message.error("上傳失敗!僅支持大小在4M以內的圖片上傳");
return false;
}
return true;
}
//上傳圖片接口
UploadImg=()=>{
let self= this;
return {
name: 'media',
action: ` `, //這里寫服務器地址
headers: { //設置請求頭
token: cookie.load('usertoken'),
},
onChange(info) {
self.setState({
uploadWaiting:true
})
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
self.setState({
uploadWaiting:false,
})
}
if (info.file.status === 'done') {
message.success(`${info.file.name}上傳成功!`);
self.setState({
uploadWaiting:false,
})
self.getData()
} else if (info.file.status === 'error') {
message.error(`${info.file.name}上傳失敗!`);
self.setState({
uploadWaiting:false,
})
}
},
}
}
<Upload {...this.UploadImg()} beforeUpload={beforeUpload}><Button>上傳圖片</Button></Upload>
列表數據相關操作(table)
constructor(props){
super(props);
this.columns=[
{title:"",
dataIndex:"ticket",
width:66,
render:(record)=>(<img src={record} width="50px" alt="" />)
},
{title:"產品名稱",dataIndex:"name"},
{title:"產品類型",dataIndex:"type", render(a, productlist, index){
return <span>{productist.type || "無"}</span>
}},
{title:"對應編碼",dataIndex:"keyword"},
{title:"生產時間",dataIndex:"createtime"},
{title:"到期時間",dataIndex:"expiretime",render(a,productlist,index){
let date=new Date(new Date(productlist.expireTime).getTime());
let nowdate=new Date(new Date().getTime());
var overtime=<span className="text-danger">已過期</span>;
var forevertime=<span className="text-success">永不</span>
var times=`${date.getFullYear()}-${date.getMonth() + 1<10?"0"+(date.getMonth()+1):date.getMonth()+1}-${date.getDate()<10?"0"+date.getDate():date.getDate()} ${date.getHours()<10?"0"+date.getHours():date.getHours()}:${date.getMinutes()<10?"0"+date.getMinutes():date.getMinutes()}:${date.getSeconds()<10?"0"+date.getSeconds():date.getSeconds()}`
return <span>{productlist.mode===1?(date<nowdate?overtime:times):forevertime}</span>
}},
{title:"操作",
dataIndex:"operation",
key:"operation",
width:225,
render:(a, qrcodelist, index)=>{
return <span>
<a href="# " className="mr-10px" onClick={()=>{this.copyLink(qrcodelist)}}>復制鏈接</a>
<a href={`${productlist.ticket}`} target={"_blank"} download={`${productlist.name}`} className="mr-10px">下載產品</a>
<Link to={`/index/setting/product/product-edit/${productlist.id}?id=${productlist.id}`} className="mr-10px">編輯</Link>
{productlist.mode===2 ? <a href="# " onClick={()=>{this.showModal2(productlist)}}>刪除</a> : <a href="# " onClick={() => {this.extendProduct(productlist)}}>延時上架</a>}
</span>
}
},
];
this.state={
visible:false,
visible2:false,
productlist:[],
filter: {
isloading: false,
name:"",
total: 0,
page:1,
pageSize:20,
}
}
}
<Table
columns={this.columns}
dataSource={this.state.productlist}
rowKey={record=>record.id} //表中的每個記錄應該有唯一的“key”支持,或者將“rowKey”設置為唯一的主鍵,否則會報錯。這里是給表格設置rowKey
pagination={{onChange:this.onChangePage, current: this.state.filter.page, pageSize: this.state.filter.pageSize , total: this.state.filter.total }} />
注意:這里涉及到兩種render寫法,兩種寫法的this指向不同,其他效果差不多
1、render:(a,b)=>{
let time=a,
let overtime=b,
return <div>......</div>
}
2、render(a,b){
let time=a,
let overtime=b,
return <div>......</div>
}
滾動監聽及回到頂部
state={
backTop:false,
backTopShow:false,
}
componentWillMount=()=>{
window.addEventListener("scroll",this.handleScroll,true) //滾動監聽
}
handleScroll=()=>{
if(document.documentElement.scrollTop>1000){
this.setState({
backTopShow:true, //當滾動到距離頂部1000px的距離時,“回到頂部”按鈕顯示
})
}else{
this.setState({
backTopShow:false,
})
}
if(document.documentElement.scrollTop===0){ //當滾動到 0時,清除定時器
clearInterval(this.scrollTimes)
}
}
backToTop=()=>{
if(document.documentElement.scrollTop>0){
this.scrollTimes=setInterval(()=>{
document.documentElement.scrollTop=document.documentElement.scrollTop-60;
},5)
}
}
{this.state.backTopShow?<Button onClick={this.backToTop}>回到頂部</Button>:null} //“回到頂部”按鈕
關於接口調用函數的寫法
間接調用,被動觸發式用這種寫法,這種寫法用onClick等主動觸發可能會引起Cannot read property 'setState' of undefined錯誤
async userInfoGet(){
let res=await api.getUserInfo()
if(res.code===0){
this.setState({
userNickname:res.data.nickname
})
}else{
console.log(api.stateCode[res.code]);
}
}
componentWillMount(){
this.userInfoGet()
} //在別的函數里間接觸發
直接調用,主動觸發式如onClick等,用這種寫法
userInfoGet=async()=>{
let res=await api.getUserInfo()
if(res.code===0){
this.setState({
userNickname:res.data.nickname
})
}else{
console.log(api.stateCode[res.code]);
}
}
<Button onClick={this.userInfoGet}></Button> //主動調用
將React.Component外部的代碼移入到其內部
外部:
const props = {
name: 'file',
action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
headers: {
authorization: 'authorization-text',
},
onChange(info) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
export default class Demo extends React.Component{}
內部:
export default class Demo extends React.Component{
state={}
pluginZipUpload=()=>{
let self=this; //這里改動 let self=this
return{ //這里改動return
name: 'plugin',
action: "https://www.mocky.io/v2/5cc8019d300000980a055e76",
headers:{
authorization: 'authorization-text',
},
onChange(info) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
self.setState({
pluginAddFile:Object.assign(self.state.pluginAddFile,{ //這里this換成self
plugin: info.file.originFileObj
})
})
console.log(self.state.pluginAddFile.plugin)
} else if (info.file.status === 'error') {
message.error(`${info.file.name}上傳失敗`);
}
},
}
};
}
如何知道函數里需要傳幾個參數或者判斷函數里有幾個參數
es5里可以用arguments打印
es6里可以用拓展運算符...a方法
在函數里寫入...params
handleUpload=(...a)=>{}
console.log(...a)即可打印出來里面需要的參數
可搭配debugger使用