写代码是一件令人兴奋的事情,特别是对于  .NET 开发人员来说,平台越来越智能化了。我们现在默认在 .NET SDK 中包含丰富的诊断和代码建议。在您需要安装 NuGet 包或其他独立工具来进行更多的代码分析之前。现在,您将在新的 .NET 5 SDK 中自动获得这些内容。

  过去,我们一直不愿意向 C# 添加新的警告。这是因为,对于将警告视为错误的用户来说,添加新的警告从技术上来说是一种对源代码的影响。然而,这些年来,在我们遇到的很多情况中,我们也确实想警告人们有些地方出了问题,从常见的编码错误到常见的 API 误用等等。

  从 .NET 5 开始,我们在 C# 编译器中引入了 AnalysisLevel,以一种安全的方式引入新的警告。所有针对 .NET 5 的项目的 AnalysisLevel 默认将被设置为 5,这意味着将引入更多的警告(以及修复它们的建议)。

  让我们讨论一下 AnalysisLevel 可能的值在您的项目中意味着什么。首先我们要注意的是:除非你覆盖默认值,否则 AnalysisLevel 是基于你的目标框架设置的:

目标框架 默认值
net5.0 5
netcoreapp3.1 or lower 4
netstandard2.1 or lower 4
.NET Framework 4.8 or lower 4

  但是,0-3 呢?下面是对每个分析级别值含义的更详细的细分:

AnalysisLevel 对C#编译器的影响 高级平台API分析
5 获得新的编译器语言分析(详细内容如下) Yes
4 与之前版本中向 C# 编译器传递 -warn:4 相同 No
3 与之前版本中向 C# 编译器传递 -warn:3 相同 No
2 与之前版本中向 C# 编译器传递 -warn:2 相同 No
1 与之前版本中向 C# 编译器传递 -warn:1 相同 No
0 与之前版本中向 C# 编译器传递 -warn:0 一样,关闭所有发出警告 No

  由于 AnalysisLevel 与项目的目标框架绑定在一起,除非你改变了你的代码目标框架,否则你永远不会改变默认的分析级别。不过,你可以手动设置分析级别。例如,即使我们的目标是 .NET Core App 3.1 或 .NET Standard (因此 AnalysisLevel 默认为 4),你仍然可以选择更高的级别。这里有一个例子:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<!-- get more advanced warnings for this project -->
<AnalysisLevel>5</AnalysisLevel>
</PropertyGroup> </Project>

  如果你想要最高的分析级别,你可以在你的项目文件中指定 latest:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<!-- be automatically updated to the newest stable level -->
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup> </Project>

  如果你很有冒险精神,并且希望尝试实验性的编译器和平台分析,那么可以指定 preview 来获得最新的、最前沿的代码诊断。

  请注意,当您使用 latest 或 preview 时,分析结果可能会因机器而异,这取决于可用的 SDK 和它提供的最高分析级别。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<!-- be opted into experimental code correctness warnings -->
<AnalysisLevel>preview</AnalysisLevel>
</PropertyGroup> </Project>

  最后,也可以设置为 none,这意味着“我不想看到任何新的警告”。在这种模式下,你不会得到任何高级 API 分析,也不会得到新的编译器警告。如果你需要更新框架,但还没有准备好接受新的警告,那么这将非常有用。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5</TargetFramework>
<!-- I am just fine thanks -->
<AnalysisLevel>none</AnalysisLevel>
</PropertyGroup> </Project>

  你还可以在 Visual Studio 中通过 Code Analysis 属性页配置项目的分析级别。只需从解决方案资源管理器导航到项目属性页。然后转到 Code Analysis 选项卡。

  在未来,我们将为 .NET 的每个版本添加一个新的分析级别。目标是确保给定的分析级别总是表示相同的默认分析集(规则及其严重性)。如果我们想在默认情况下启用现有的规则,我们将在即将到来的分析级别中这样做,而不是更改现有的级别。这确保了已有的项目/源代码总是产生相同的警告,不管 SDK 有多新(当然,除了项目使用 preview 或 latest)。

  由于所有的 .NET 5 项目都将进入分析级别 5,让我们来看看一些新的警告和建议。

分析级别 5 中出现的所有新的警告和错误

  粗体部分将在 .NET 5 发布的时候进入第 5 级。剩下的是 Visual Studio 2019 16.8 预览2 中的 .NET 5 预览8 中的新警告!

常见错误的警告

  第一组新的警告旨在发现潜在的错误,通常是在较大的代码库中。现在不需要额外的编译器分析就可以很容易地发现它们。

当表达式永真或永假时发出警告

  这种新的警告非常普遍,考虑以下代码:

public void M(DateTime dateTime)
{
if (dateTime == null) // warning CS8073
{
return;
}
}

  DateTime 是一个结构体,结构体不能为空。从 .NET 开始,我们将在 CS8073 中警告这种情况。警告信息是:

  Warning CS8073: The result of the expression is always ‘false’ since the value of type ‘DateTime’ is never equal to ‘null’ of type ‘DateTime?’

  很明显,这段代码所做的事情没有意义,但是考虑到这样的检查可能发生在有多个参数要验证的方法中。要解决这个问题,你可以删除代码(因为它总是假的,它没有做任何事情),或者改变它的类型为 DateTime? 如果参数的预期值为 null。

public void M(DateTime? dateTime) // We accept a null DateTime
{
if (dateTime == null) // No Warnings
{
return;
}
}

不允许在静态类型上用as、 is

  下面是一个很好的小改进:

static class Fiz
{
} class P
{
bool M(object o)
{
return o is Fiz; // CS7023
}
}

  因为 Fiz 是一个静态类,所以像 o 这样的实例对象永远不可能是这种类型的实例。我们会收到这样的警告:

  Warning CS7023 The second operand of an ‘is’ or ‘as’ operator may not be static type ‘Fiz’

  解决这个问题的方法是重构我们的代码(也许我们一开始就检查错类型了),或者让类 Fiz 是非静态的:

class Fiz
{
} class P
{
bool M(object o)
{
return o is Fiz; // no error
}
}

不允许锁定非引用类型

  锁定非引用类型(比如 int)什么也做不了,因为它们是按值传递的,所以每个堆栈帧上都有不同版本的非引用类型。在过去,对于像 lock(5) 这样简单的情况,我们会警告你对非引用类型的锁定,但是直到最近,我们对泛型方法的也支持警告:

public class P
{
public static void GetValue<TKey>(TKey key)
{
lock (key) // CS0185
{
}
} static void Main()
{
GetValue(1);
}
}

  这是一个错误,因为传入 int(在这个不受约束的泛型中允许)实际上不会正确锁定。我们会看到这个错误:

  Error CS0185 ‘TKey’ is not a reference type as required by the lock statement

  要解决这个问题,我们需要指出 GetValue 方法应该只提供引用类型。我们可以使用泛型类型约束来做到这一点,where TKey : class

public class P
{
public static void GetValue<TKey>(TKey key) where TKey : class
{
lock (key) // no error
{
}
}
}

重新抛出以保留堆栈细节

  我们都是“优秀的”开发人员,所以我们的代码不会抛出异常,对吗?好吧,即使是最好的开发人员也需要处理异常,而新程序员常陷入的一个陷阱是:

try
{
throw new Exception();
}
catch (Exception ex)
{
// probably logging some info here... // rethrow now that we are done
throw ex; // CA2200
}

  在学校里,我学到如果有人向我扔球,我接住它,我必须把球扔回去!像这样的比喻让很多人相信 throw ex 是重新抛出这个异常的正确方式。遗憾的是,这将改变原来异常中的堆栈。现在您将收到一个警告,说明正在发生这种情况。它是这样的:

  Warning CA2200 Re-throwing caught exception changes stack information

  在几乎所有情况下,这里要做的正确事情是简单地使用 throw 关键字,而不提及我们捕获的异常的变量。

try
{
throw new Exception();
}
catch (Exception ex)
{
// probably logging some info here... // rethrow now that we are done
throw;
}

  我们还提供了一个代码修复,可以轻松地在您的文档、项目或解决方案中一次性修复所有这些问题!

不要在值类型中使用 ReferenceEquals

  Equality 在 .NET 中是一个棘手的话题。下一个警告试图使意外地通过引用比较一个 struct 。考虑以下代码:

int int1 = 1;
int int2 = 1;
Console.WriteLine(object.ReferenceEquals(int1, int2)); // warning CA2013

  这将装箱两个 int,而 ReferenceEquals 将总是返回 false 作为结果。我们将看到这个警告描述:

  Warning CA2013: Do not pass an argument with value type ‘int’ to ‘ReferenceEquals’. Due to value boxing, this call to ‘ReferenceEquals’ will always return ‘false’.

  解决此错误的方法是使用相等运算符 == 或 object.Equals:

int int1 = 1;
int int2 = 1;
Console.WriteLine(int1 == int2); // using the equality operator is fine
Console.WriteLine(object.Equals(int1, int2)); // so is object.Equals

跟踪跨程序集中结构的明确赋值(definite assignment)

  很多人可能会惊讶地发现,下一个警告其实并不算是警告:

using System.Collections.Immutable;

class P
{
public void M(out ImmutableArray<int> immutableArray) // CS0177
{
}
}

  这条规则是关于明确赋值的,这是 C# 中一个有用的特性,可以确保你不会忘记给变量赋值。

  Warning CS0177: The out parameter ‘immutableArray’ must be assigned to before control leaves the current method

  目前已经针对几种不同的情况发布了 CS0177,但不是前面展示的情况。这里的历史是,这个 bug 可以追溯到 C# 编译器的原始实现。以前,C# 编译器在计算明确赋值时忽略从元数据导入的值类型中的引用类型的私有字段。这个非常特殊的错误意味着像 ImmutableArray 这样的类型能够逃脱明确赋值分析。

  现在编译器将正确的显示错误,你可以修复它,只要确保它总是分配一个值,像这样:

using System.Collections.Immutable;

class P
{
public bool M(out ImmutableArray<int> immutableArray) // no warning
{
immutableArray = ImmutableArray<int>.Empty;
}
}

.NET API 使用错误的警告

  下面示例是关于正确使用 .NET 库的。分析级别可以防止现有的 .NET API 的不当使用,但它也会影响 .NET 库的发展。如果设计了一个有用的 API,但它有可能被误用,那么还可以在新增 API 的同时添加一个检测误用的新警告。

不要给从 MemoryManager 的派生类定义终结器

  当你想实现自己的 Memory<T> 类型时,MemoryManager 是一个有用的类。这不是你经常做的事情,但是当你需要它的时候,你真的需要它。这个新的警告会触发这样的情况:

class DerivedClass <T> : MemoryManager<T>
{
public override bool Dispose(bool disposing)
{
if (disposing)
{
_handle.Dispose();
}
} ~DerivedClass() => Dispose(false); // warning CA2015
}

  向这种类型添加终结器可能会在垃圾收集器中引入漏洞,这是我们都希望避免的!

  Warning CA2015 Adding a finalizer to a type derived from MemoryManager<T> may permit memory to be freed while it is still in use by a Span<T>.

  解决方法是删除这个终结器,因为它会在你的程序中导致非常细微的 bug,很难找到和修复。

class DerivedClass <T> : MemoryManager<T>
{
public override bool Dispose(bool disposing)
{
if (disposing)
{
_handle.Dispose();
}
}
// No warning, since there is no finalizer here
}

参数传递给 TaskCompletionSource,调用错误的构造函数

  这个警告告诉我们,我们使用了错误的枚举。

var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); // warning CA2247

  除非你已经意识到这个问题,否则你可能会在盯着它看一会儿才能发现。问题是这样的,这个构造函数不接受 TaskContinuationOptions 枚举,它接受 TaskCreationOptions 枚举。发生的事情是,我们正在调用的 TaskCompletionSource 的构造函数接受 object 类型参数!考虑到它们的名称特别相似,并且它们的值也非常相似,所以这种错误容易发生。

  Warning CA2247: Argument contains TaskContinuationsOptions enum instead of TaskCreationOptions enum.

  修复它只需要传递正确的枚举类型:

var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); // no warning

当代码不能在所有平台上工作时发出警告

  这个真是太棒了!我不会在这里详细讨论它的复杂之处(期待以后关于这个主题的博客文章)。但这里警告的目的是让您知道,您正在调用的 api 可能无法在您正在构建的所有目标上工作。

  假设我有一个同时在 Linux 和 Windows 上运行的应用程序。我有一个方法,我使用它来获得路径来创建日志文件,根据运行环境,它有不同的行为。

private static string GetLoggingPath()
{
var appDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var loggingDirectory = Path.Combine(appDataDirectory, "Fabrikam", "AssetManagement", "Logging"); // Create the directory and restrict access using Windows
// Access Control Lists (ACLs). var rules = new DirectorySecurity(); // CA1416
rules.AddAccessRule(
new FileSystemAccessRule(@"fabrikam\log-readers",
FileSystemRights.Read,
AccessControlType.Allow)
);
rules.AddAccessRule(
new FileSystemAccessRule(@"fabrikam\log-writers",
FileSystemRights.FullControl,
AccessControlType.Allow)
); if (!OperatingSystem.IsWindows())
{
// Just create the directory
Directory.CreateDirectory(loggingDirectory);
}
else
{
Directory.CreateDirectory(loggingDirectory, rules);
} return loggingDirectory;
}

  我正确地使用了 OperatingSystem.IsWindows() 来检查操作系统是否是 Windows 操作系,但是实际上 if 分支之前已经使用了平台特定的 API,将不能工作在 Linux!

  Warning CA1416: ‘DirectorySecurity’ is unsupported on ‘Linux’

  处理这个问题的正确方法是将所有特定于平台的代码移动到 else 语句中。

private static string GetLoggingPath()
{
var appDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var loggingDirectory = Path.Combine(appDataDirectory, "Fabrikam", "AssetManagement", "Logging"); if (!OperatingSystem.IsWindows())
{
// Just create the directory
Directory.CreateDirectory(loggingDirectory);
}
else
{
// Create the directory and restrict access using Windows
// Access Control Lists (ACLs). var rules = new DirectorySecurity(); // CA1416
rules.AddAccessRule(
new FileSystemAccessRule(@"fabrikam\log-readers",
FileSystemRights.Read,
AccessControlType.Allow)
);
rules.AddAccessRule(
new FileSystemAccessRule(@"fabrikam\log-writers",
FileSystemRights.FullControl,
AccessControlType.Allow)
); Directory.CreateDirectory(loggingDirectory, rules);
} return loggingDirectory;
}

低级编码帮助

  在编写高性能应用程序时,还有一些有用的警告。下面这些警告确保您不需要为这些情况牺牲安全性。

P/Invoke 时不要在 string 参数上使用 OutAttribute

  有时你需要与本地代码进行互操作。.NET 有使用平台调用服务的概念(P/ Invoke)来简化这个过程。但是,在 .NET 中,在向本地库发送数据和从本地库发送数据方面存在一些问题。考虑以下代码:

[DllImport("MyLibrary")]
private static extern void Goo([Out] string s); // warning CA1417

  除非您非常熟悉 P/Invoke 的编写,否则这里的错误并不明显。通常将 OutAttribute 应用于运行时不知道的类型,以指示应该如何封送类型。OutAttribute 表示您正在按值传递数据。字符串按值传递没有意义,而且有可能导致运行时崩溃。

  Warning CA1417 Do not use the ‘OutAttribute’ for string parameter ‘s’ which is passed by value. If marshalling of modified data back to the caller is required, use the ‘out’ keyword to pass the string by reference instead.

  解决这个问题的方法是将其作为一个普通的 out 参数(通过引用传递)来处理。

[DllImport("MyLibrary")]
private static extern void Goo(out string s); // no warning

  或者如果你不需要将字符串封送回调用者,你可以这样做:

[DllImport("MyLibrary")]
private static extern void Goo(string s); // no warning

在适当情况下,string 使用 AsSpan 而不是基于范围的索引器

  这都是为了确保您不会意外地分配字符串。

class Program
{
public void TestMethod(string str)
{
ReadOnlySpan<char> slice = str[1..3]; // CA1831
}
}

  在上面的代码中,开发者的意图是使用 C# 中新的基于范围的索引特性来索引一个字符串。不幸的是,这实际上会分配一个字符串,除非您首先将该字符串转换为 span。

  Warning CA1831 Use ‘AsSpan’ instead of the ‘System.Range’-based indexer on ‘string’ to avoid creating unnecessary data copies

  解决方法是在这种情况下添加 AsSpan 调用:

class Program
{
public void TestMethod(string str)
{
ReadOnlySpan<char> slice = str.AsSpan()[1..3]; // no warning
}
}

不要在循环中使用 stackalloc

  stackalloc 关键字非常适合于确保正在进行的操作对垃圾收集器来说比较容易。在过去,stackalloc 关键字用于不安全的代码上下文中,以便在堆栈上分配内存块。但自从 C# 8 以来,它也被允许在 unsafe 的块之外,只要这个变量被分配给一个 Span<T> 或一个 ReadOnlySpan<T>。

class C
{
public void TestMethod(string str)
{
int length = 3;
for (int i = 0; i < length; i++)
{
Span<int> numbers = stackalloc int[length]; // CA2014
numbers[i] = i;
}
}
}

  在堆栈上分配大量内存可能会导致著名的 StackOverflow 异常,即我们在堆栈上分配的内存超过了允许的范围。在循环中分配尤其危险。

  Warning CA2014 Potential stack overflow. Move the stackalloc out of the loop.

  解决方法是将 stackalloc 移出循环:

class C
{
public void TestMethod(string str)
{
int length = 3;
Span<int> numbers = stackalloc int[length]; // no warning
for (int i = 0; i < length; i++)
{
numbers[i] = i;
}
}
}

设置分析级别

  现在您已经看到了这些警告的重要性,您可能永远不想回到一个没有它们的世界,对吗?我知道世界并不总是这样运转的。正如在这篇文章的开头提到的,这些都是打破源代码的改变,你应该在适合自己的时间表中完成它们。我们现在介绍这个的部分原因是为了得到两个方面的反馈:

  1. 我们提出的这一小部分警告是否太过破坏性

  2. 调整警告的机制是否足以满足您的需要

回到 .NET Core 3.1 的分析等级

  如果你只想回到 .NET 5 之前的状态(即.NET Core 3.1 中的警告级别),你所需要做的就是在你的项目文件中将分析级别设置为4。下面是一个例子:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<!-- get the exact same warnings you used to -->
<AnalysisLevel>4</AnalysisLevel>
</PropertyGroup> </Project>

只关闭一个规则

  如果有一个你认为不适用于你的代码库的特定警告,你可以使用一个 editorconfig 文件来关闭它。你可以通过在错误列表中将警告的严重性设置为“none”来做到这一点。

  或者从编辑器中出现警告的灯泡菜单中选择“None”

关闭警告的单个实例

  如果你大部分时间都想使用这个警告,但在少数情况下要关闭它,你可以使用灯泡菜单中的一个:

  在源码中禁止

  在单独的禁止文件中禁止它

  在源码中禁用并标记一个特性

总结

  我们希望你对 .NET 5 代码分析的改进感到兴奋,请给我们一些反馈。

原文链接

  https://devblogs.microsoft.com/dotnet/automatically-find-latent-bugs-in-your-code-with-net-5/

【译】自动发现 .NET 5 中代码的潜在错误的更多相关文章

  1. zabbix自动发现监控url

    1.在监控客户机上 web_site_code_status.sh: #!/bin/bash UrlFile="/opt/scripts/WEB.txt" IFS=$'\n' we ...

  2. Zabbix自动发现与自动注册.

    一, 自动发现与自动注册 自动发现? 当场景中出现要添加很多台主机的时候,一台台添加难免太过于繁琐,zabbix提供自动注册,自动发现,可以实现主机的批量添加, zabbix的发现包括三种类型: # ...

  3. Prometheus Operator 自动发现和持久化

    Prometheus Operator 自动发现和持久化 之前在 Prometheus Operator 下面自定义一个监控选项,以及自定义报警规则的使用.那么我们还能够直接使用前面课程中的自动发现功 ...

  4. Zabbix自动发现并监控磁盘IO、报警

    本文转载自: https://www.93bok.com 引言 Zabbix并没有提供模板来监控磁盘的IO性能,所以我们需要自己来创建一个,由于一台服务器中磁盘众多,如果只有一两台可以手动添加,但服务 ...

  5. nose在python2与python3中的包的自动发现用例的区别

    最近在使用python3,同样装了nose,发现自动发现用例总是有问题,如下面的代码结婚 testcase |------ __init__.py |------ test_bb.py test_bb ...

  6. Eclipse中代码自动提示功能设置

    Eclipse中代码自动提示功能设置 1 打开eclipse→Windows→Preferences→Java→Editor→Content Assist: 修改Auto Activation tri ...

  7. Google自动广告,将广告代码放置在 HTML 中的什么位置?

    Google自动广告,将广告代码放置在 HTML 中的什么位置? 为自动广告生成广告代码后,您需要将此代码放置在要展示广告的每个网页中.您应将广告代码放置在网页的 <head> 标记(或正 ...

  8. django2自动发现项目中的url

    根据路飞学城luffycity.com 的crm项目修改的 1 url入口:rbac/urls.py urlpatterns = [ ... # 批量操作权限 re_path(r'^multi/per ...

  9. 在IDEA中代码自动提示第一个字母大小写必须匹配的解决

    在IDEA中代码自动提示第一个字母大小写必须匹配的解决 学习了:http://blog.csdn.net/babys/article/details/41775715 setting>Edito ...

随机推荐

  1. Agumaster 改善了pagination

    页面越来越完善了.

  2. 关于Vue-loader的那些事儿

    什么是Vue-loader 一个webpack的加载器,负责将vue组件编译成普通的JavaScript模块. 关于webpack的介绍 这里呢?用到webpack,在项目的编译打包的过程中,将复杂的 ...

  3. leetcode刷题-50Pow(x, n)

    题目 实现 pow(x, n) ,即计算 x 的 n 次幂函数. 思路 最初的想法n>0计算res = res*x 计算n次,n<0,将x取倒数后同理,但结果表明计算速度太慢了. 后续应该 ...

  4. C# 读取 ttf字体文件里的 Unicode

    因为爬虫要解析 &#x880cc这种字体编码的值,下载到一个ttf文件,用百度字体编辑器 打开,可以看到每个字符对应的Unicode (数字下方 $23.$2A...这些), 我需要拿到这些映 ...

  5. .NET性能排查

    概述 1,性能参数 2,性能排查方式 3,.NET的性能分析工具 1,性能指标 一个系统的性能排查或者性能设计的前提就是要有明确的性能指标:常见的性能参数 1.响应时间(处理任务时的延迟,简称 RT, ...

  6. VMware安装Centos7并联网使用

    一.安装VMware VMwareworkstation官方下载地址: https://www.vmware.com/cn/products/workstation-pro/workstation-p ...

  7. Solr专题(三)SSM项目整合Solr

    一.环境配置 所需要的jar包: org.apache.solr.solr-solrj maven依赖: <!-- https://mvnrepository.com/artifact/org. ...

  8. [LeetCode]198. 打家劫舍(DP)

    题目 你是一个专业的小偷,计划偷窃沿街的房屋.每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警. 给定一个 ...

  9. Springboot+Vue实现仿百度搜索自动提示框匹配查询功能

    案例功能效果图 前端初始页面 输入搜索信息页面 点击查询结果页面 环境介绍 前端:vue 后端:springboot jdk:1.8及以上 数据库:mysql 核心代码介绍 TypeCtrler .j ...

  10. 【vue】---- 新版脚手架搭建项目流程详解

    一.概述 本文将介绍vue项目完整的搭建流程,在使用新版本的脚手架基础上,进行了一系列的完善和配置.主要内容如下: 1.项目初始化 安装脚手架 创建项目 项目结构 2.项目搭建配置 引入第三方插件 路 ...