确保对象键属于已定义的集合

时间:2019-12-17 21:29:04

标签: typescript

我正在尝试创建一个只能接受一组定义的键的对象类型。在下面的示例中,//************************************************************************************************/ //* VR Smart Grid Lite for mt5.mq5 */ //* Copyright 2019, Trading-go Project. */ //* Author: Voldemar, Version: 14.05.2019, Site https://trading-go.ru */ //************************************************************************************************/ //* */ //************************************************************************************************/ //VR Smart Grid https://www.mql5.com/ru/market/product/28140 //VR Smart Grid Lite https://www.mql5.com/ru/code/20223/ //VR Smart Grid MT5 https://www.mql5.com/ru/market/product/38626/ //VR Smart Grid Lite MT5 https://www.mql5.com/ru/code/25528/ //Blog RU https://www.mql5.com/ru/blogs/post/726568 //Blog EN https://www.mql5.com/en/blogs/post/726569 //************************************************************************************************/ //| All products of the Author https://www.mql5.com/ru/users/voldemar/seller //************************************************************************************************/ #property copyright "Copyright 2019, Trading-go Project." #property link "https://trading-go.ru" #property version "19.050" #property description " " #include <Trade\PositionInfo.mqh> CPositionInfo m_position; #include <Trade\Trade.mqh> CTrade trade; //************************************************************************************************/ //* */ //************************************************************************************************/ input int iTakeProfit = 300; // Take Profit (in pips) input double iStartLots = 0.01; // Start lot input double iMaximalLots = 2.56; // Maximal Lots input int iPointOrderStep = 390; // Point order step (in pips) input int iMinimalProfit = 70; // Minimal profit for close grid (in pips) input int iMagicNumber = 227; // Magic Number (in number) input int iSlippage = 30; // Slippage (in pips) //--- //************************************************************************************************/ //* */ //************************************************************************************************/ int OnInit() { Comment(""); trade.LogLevel(LOG_LEVEL_ERRORS); trade.SetExpertMagicNumber(iMagicNumber); trade.SetDeviationInPoints(iSlippage); trade.SetMarginMode(); trade.SetTypeFillingBySymbol(Symbol()); return(INIT_SUCCEEDED); } //************************************************************************************************/ //* */ //************************************************************************************************/ void OnTick() { double BuyPriceMax=0,BuyPriceMin=0,BuyPriceMaxLot=0,BuyPriceMinLot=0, SelPriceMin=0,SelPriceMax=0,SelPriceMinLot=0,SelPriceMaxLot=0; ulong BuyPriceMaxTic=0,BuyPriceMinTic=0,SelPriceMaxTic=0,SelPriceMinTic=0; double op=0,lt=0,tp=0; ulong tk=0; int b=0,s=0; int total=PositionsTotal(); for(int k=total-1;k>=0;k--) if(m_position.SelectByIndex(k)) if(m_position.Symbol()==Symbol()) if(m_position.Magic()==iMagicNumber) if(m_position.PositionType()==POSITION_TYPE_BUY || m_position.PositionType()==POSITION_TYPE_SELL) { op=NormalizeDouble(m_position.PriceOpen(),Digits()); lt=NormalizeDouble(m_position.Volume(),2); tk=m_position.Ticket(); if(m_position.PositionType()==POSITION_TYPE_BUY) { b++; if(op>BuyPriceMax || BuyPriceMax==0) { BuyPriceMax = op; BuyPriceMaxLot = lt; BuyPriceMaxTic = tk; } if(op<BuyPriceMin || BuyPriceMin==0) { BuyPriceMin = op; BuyPriceMinLot = lt; BuyPriceMinTic = tk; } } // === if(m_position.PositionType()==POSITION_TYPE_SELL) { s++; if(op>SelPriceMax || SelPriceMax==0) { SelPriceMax = op; SelPriceMaxLot = lt; SelPriceMaxTic = tk; } if(op<SelPriceMin || SelPriceMin==0) { SelPriceMin = op; SelPriceMinLot = lt; SelPriceMinTic = tk; } } } //*************************************************************// double AwerageBuyPrice=0,AwerageSelPrice=0; if(b>=2) AwerageBuyPrice=NormalizeDouble((BuyPriceMax*BuyPriceMaxLot+BuyPriceMin*BuyPriceMinLot)/(BuyPriceMaxLot+BuyPriceMinLot)+iMinimalProfit*Point(),Digits()); if(s>=2) AwerageSelPrice=NormalizeDouble((SelPriceMax*SelPriceMaxLot+SelPriceMin*SelPriceMinLot)/(SelPriceMaxLot+SelPriceMinLot)-iMinimalProfit*Point(),Digits()); //*************************************************************// double BuyLot=0,SelLot=0; if(BuyPriceMinLot==0) BuyLot=iStartLots; else BuyLot=BuyPriceMinLot*2; if(SelPriceMaxLot==0) SelLot=iStartLots; else SelLot=SelPriceMaxLot*2; //*************************************************************// if(BuyLot>iMaximalLots) BuyLot=iMaximalLots; if(SelLot>iMaximalLots) SelLot=iMaximalLots; if(!CheckVolumeValue(BuyLot) || !CheckVolumeValue(SelLot)) return; //*************************************************************// MqlRates rates[]; CopyRates(Symbol(),PERIOD_CURRENT,0,2,rates); MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) Print("SymbolInfoTick() failed, error = ",GetLastError()); if(rates[1].close>rates[1].open) if((b==0) || (b>0 && (BuyPriceMin-tick.ask)>(iPointOrderStep*Point()))) if(!trade.Buy(NormalizeDouble(BuyLot,2))) Print("OrderSend error #",GetLastError()); if(rates[1].close<rates[1].open) if((s==0) || (s>0 && (tick.bid-SelPriceMax)>(iPointOrderStep*Point()))) if(!trade.Sell(NormalizeDouble(SelLot,2))) Print("OrderSend error #",GetLastError()); //*************************************************************// for(int k=total-1;k>=0;k--) if(m_position.SelectByIndex(k)) if(m_position.Symbol()==Symbol()) if(m_position.Magic()==iMagicNumber) if(m_position.PositionType()==POSITION_TYPE_BUY || m_position.PositionType()==POSITION_TYPE_SELL) { op=NormalizeDouble(m_position.PriceOpen(),Digits()); tp=NormalizeDouble(m_position.TakeProfit(),Digits()); lt=NormalizeDouble(m_position.Volume(),2); tk=m_position.Ticket(); if(m_position.PositionType()==POSITION_TYPE_BUY && b==1 && tp==0) if(!trade.PositionModify(tk,m_position.StopLoss(),NormalizeDouble(tick.ask+iTakeProfit*Point(),Digits()))) Print("OrderModify error #",GetLastError()); if(m_position.PositionType()==POSITION_TYPE_SELL && s==1 && tp==0) if(!trade.PositionModify(tk,m_position.StopLoss(),NormalizeDouble(tick.bid-iTakeProfit*Point(),Digits()))) Print("OrderModify error #",GetLastError()); if(m_position.PositionType()==POSITION_TYPE_BUY && b>=2) { if(tk==BuyPriceMaxTic || tk==BuyPriceMinTic) if(tick.bid<AwerageBuyPrice && tp!=AwerageBuyPrice) if(!trade.PositionModify(tk,m_position.StopLoss(),AwerageBuyPrice)) Print("OrderModify error #",GetLastError()); if(tk!=BuyPriceMaxTic && tk!=BuyPriceMinTic && tp!=0) if(!trade.PositionModify(tk,0,0)) Print("OrderModify error #",GetLastError()); } if(m_position.PositionType()==POSITION_TYPE_SELL && s>=2) { if(tk==SelPriceMaxTic || tk==SelPriceMinTic) if(tick.ask>AwerageSelPrice && tp!=AwerageSelPrice) if(!trade.PositionModify(tk,m_position.StopLoss(),AwerageSelPrice)) Print("OrderModify error #",GetLastError()); if(tk!=SelPriceMaxTic && tk!=SelPriceMinTic && tp!=0) if(!trade.PositionModify(tk,0,0)) Print("OrderModify error #",GetLastError()); } } } //************************************************************************************************/ //* */ //************************************************************************************************/ void OnDeinit(const int reason) { } //************************************************************************************************/ //* */ //************************************************************************************************/ bool CheckVolumeValue(double volume) { //--- минимально допустимый объем для торговых операций double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); if(volume<min_volume) return(false); //--- максимально допустимый объем для торговых операций double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); if(volume>max_volume) return(false); //--- получим минимальную градацию объема double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP); int ratio=(int)MathRound(volume/volume_step); if(MathAbs(ratio*volume_step-volume)>0.0000001) return(false); return(true); } //************************************************************************************************/ //* */ //************************************************************************************************/

假设'1.1.1' | '1.1.2' | '1.1.3'是集合中的有效字符串。

levelNum, subLevelNum and problemNum

但是,如果我按如下方式编写错误行,则它会起作用:

type AllowedIndexes = '1.1.1' | '1.1.2' | '1.1.3';
type ProblemType = { [index in AllowedIndexes]?: Problem };

let problems: ProblemType = {};

// --> the following line gives the error <--
// `TS2322: Type 'string' is not assignable to type AllowedIndex.`
let ix:AllowedIndexes = `${levelNum}.${subLevelNum}.${problemNum}`;

problems[ix] = new Problem(....)

我走的路正确吗,还是我完全确定对象键属于已定义的集?

2 个答案:

答案 0 :(得分:1)

你在这里是对的。

此行

let ix:AllowedIndexes = `${levelNum}.${subLevelNum}.${problemNum}`;

告诉您的TypeScript您正在尝试将模板字符串(类型为string)分配给期望类型为AllowedIndexes的值。但是,TypeScript不能确保这始终是正确的,并且需要进行修改:

let ix:AllowedIndexes = `${levelNum}.${subLevelNum}.${problemNum}` as AllowedIndexes;

您明确地告诉它该值确实为AllowedIndexes。从类型的角度来看,它是正确的,而从逻辑/运行时角度来看,当levelNumsubLevelNumproblemNum变量之一不正确时,它可能是不正确的,但是TypeScript无法知道这个案例。我能想到的对该代码的唯一改进是对Record使用内置的ProblemType类型,如下所示:

type ProblemType = Partial<Record<AllowedIndexes, Problem>>;

请注意,我之所以添加Partial只是因为您使用?将对象的道具指定为可选道具

答案 1 :(得分:0)

除了使用字符串文字类型的简单相等性之外,没有其他方法可以限制ts中字符串的内容。

也许可以改用元组类型,可以将其约束为特定长度,并使用文字类型为元组中的每个项目指定有效域。另外,如果您的零件以表单字符串开头,则可以使用自定义类型断言或类型保护来缩小类型:

type AllowedIndexesItem = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0";
type AllowedIndexes = [AllowedIndexesItem, AllowedIndexesItem, AllowedIndexesItem];

declare let problemNum: string;
declare let subLevelNum: string;
declare let levelNum: string;

function assertAllowedIndexesItem(o: string): asserts o is AllowedIndexesItem {
    if (!(o.length == 1 && o >= "0" && o < "9")) {
        throw new Error("Invlaid value")
    }
}
assertAllowedIndexesItem(levelNum);
assertAllowedIndexesItem(subLevelNum);
assertAllowedIndexesItem(problemNum);
let ix:AllowedIndexes = [levelNum, subLevelNum, problemNum];

Playground Link