主题
局部安装方法与react工程化项目目录结构分析(Day4 2/4)
本页对应 React + TypeScript + 医通项目,共 6 段视频,约 38 分钟。
学习目标
- 能按局部安装流程完成 React 工程依赖安装,并说明安装结果与常见命令的作用。
- 能分析 React 工程化项目目录结构,指出入口文件、组件目录、样式文件及资源目录的职责。
- 能使用 CSS 模块化方式为同一元素应用多个类名,并通过数组与空格连接实现多个样式生效。
- 能完成轮播图布局与交互功能,包括左右按钮切换、索引边界判断、数据驱动视图更新及图片数组化改造。
- 能写出 React 17 的入口渲染代码,并对比说明其与 React 18 在导入方式和根节点创建方式上的差异。
- 能结合组件出生、更新、销毁说明 React 生命周期的三个阶段,并列举各阶段会触发的特殊方法及执行时机。
知识点
先明确局部安装的定位
本节讲的是用“局部安装”的方式准备 create-react-app,再用它创建 React 项目。转写稿在回顾脚手架时提到:创建项目不只一种方式,前面介绍过 npm 的方式、yarn 的方式,也介绍了局部安装的方式。这里“y 的方式”应理解为 yarn 的方式,不是另一个陌生命令。局部安装的核心不是换框架,而是把创建工具装到当前项目里,避免依赖全局环境。下面按笔记顺序拆开讲。
1. 找一个非中文目录并打开终端
为什么需要:项目路径里出现中文,容易让命令行工具、依赖解析或脚本执行出现不必要的问题;初学阶段先避开这类干扰。怎么想:不要急着敲安装命令,先确认“我在哪个目录”“这个目录能不能放项目”。怎么做:新建一个纯英文、尽量无空格的目录,在该目录中打开终端。易错点:在桌面或下载目录里随手执行,结果项目生成到意想不到的位置;路径含中文或空格后,后续命令定位困难。调试时先执行查看当前路径的命令,确认终端所在位置,再继续。
2. 执行 npm init -y
为什么需要:局部安装需要项目级的 package.json 来记录依赖和项目信息。怎么想:先把这个目录初始化成一个 npm 项目,后面的安装才有落点。怎么做:在终端执行:
shell
npm init -y易错点:没看清当前目录就执行,导致在错误位置生成 package.json;目录里已有项目文件时,要先确认是否会覆盖或混用配置。若后续发现依赖没有写进当前项目,第一反应应是检查 package.json 是否在当前目录。
3. 局部安装 create-react-app
为什么需要:全局安装会把工具装到全局环境,多个项目之间可能互相影响;局部安装则把工具装进当前项目的 node_modules,依赖关系更清楚。怎么想:既然已经初始化了项目,就把创建工具作为当前项目的依赖安装。怎么做:执行:
shell
npm i create-react-app易错点:安装完成后误以为可以直接输入 create-react-app 创建项目;局部安装的工具通常不在全局命令路径里,需要借助 npx 调用。另一个易错点是没确认安装是否成功就往下走,调试时可先看当前目录是否出现 node_modules,并检查 package.json 中是否记录了依赖。
4. 通过 npx 创建项目
为什么需要:局部安装的 create-react-app 不在全局命令中,直接用原名可能找不到;npx 会优先使用当前项目本地安装的命令。怎么想:工具已经装在本地,就用 npx 去调用它,并给它一个项目名。怎么做:执行:
shell
npx create-react-app 项目名易错点:项目名尽量使用英文小写,避免中文、空格和不合规字符;命令执行位置要在刚才初始化并安装依赖的目录中。若提示找不到命令,按“当前目录是否有 package.json、是否完成局部安装、是否使用了 npx”的顺序排查,而不是急着重装。
5. 进入项目目录并启动项目
为什么需要:项目创建完成后,开发服务器要在项目目录内启动。怎么想:先 cd 进入刚生成的项目,再运行启动命令。怎么做:按笔记保留的命令执行:
shell
cd 项目名
npm start易错点:还没进入项目目录就执行 npm start,会找不到启动脚本;启动后终端不要立即关闭,开发服务器需要保持运行。若启动失败,先确认是否在项目根目录、依赖是否安装完整,再检查端口是否被占用。局部安装的完整顺序就是:选目录 → 初始化 → 局部安装工具 → 用 npx 创建 → 进目录启动。
先看整体:根目录的边界
为什么需要:React 工程化项目文件多,先分清“npm 包目录、静态资源、源码、配置文件”,后面找入口和排错才不会乱。
怎么想:按使用对象划分——node_modules 给 npm 用,public 给浏览器站点用,src 给开发者写业务用,package*.json 给依赖管理用。
怎么做:先保留本节目录树,读项目时按“public/index.html → src/index.js → src/App.js”找主链路。
shell
02-react-scaffold 项目根目录
|- node_modules npm包目录
|— public 静态资源目录
| |- favicon.ico 站点图标
| |- index.html 网站的入口html文件
| |- logo192.png 移动端图片
| |- logo512.png 移动端图片
| |- manifest.json 移动端图标的配置文件
| |- robots.txt 爬虫文件
|- src 程序员开发目录
| |- App.css 根组件的样式文件
| |- App.js 根组件App
| |- App.test.js 测试文件
| |- index.js js的入口文件
| |- index.css 入口文件的样式文件
| |- logo.svg 旋转的菊花图标
| |- reportWebVitals.js google的报告文件
| |- setupTests.js 测试启动文件
|- package-lock.json 包版本锁文件
|- package.json 包配置文件易错点:不要改 node_modules;不要手改 package-lock.json;不要把业务代码写进 public。转写稿提到脚手架创建有 npm、yarn、局部安装等方式,创建后先认目录,不要急着堆业务。
public:静态资源与网站入口
为什么需要:浏览器先拿到站点入口页,React 代码才有挂载位置。
怎么想:public 放站点级静态文件,index.html 是网站入口 html。
怎么做:favicon.ico 是站点图标;logo192.png、logo512.png 是移动端图片;manifest.json 是移动端图标配置;robots.txt 是爬虫文件。
易错点:不要以为 index.html 里要写业务组件;这些文件名不要随意改。转写稿里讲入口差异时提到 React18 从 react-dom/client 创建根节点,React17 用 react-dom 的 ReactDOM.render,这最终都挂在入口页提供的容器上。
src:程序员开发目录
为什么需要:源码要集中管理,入口、根组件、样式、测试文件各自有位置。
怎么想:index.js 是 js 入口,App.js 是根组件,App.js 再组织组件树。
怎么做:index.js 负责入口挂载;App.js 是根组件;App.css 服务根组件;index.css 服务入口;logo.svg 是旋转菊花图标;App.test.js 是测试文件;reportWebVitals.js 是 Google 报告文件;setupTests.js 是测试启动文件。
易错点:不要把 App.js 当入口直接运行;不要在 index.js 里写大量业务;测试文件不要误当业务代码。转写稿演示过:18 项目写 17 的 ReactDOM.render 仍能运行,但会提示“no longer supported”,所以看 index.js 能判断项目写法。
包管理文件:package.json 与 package-lock.json
为什么需要:记录依赖和锁定版本,保证团队安装结果一致。
怎么想:package.json 是包配置,package-lock.json 是包版本锁文件。
怎么做:安装依赖看 package.json;提交代码保留 lock 文件;升级依赖用包管理器命令,不手写 lock。
易错点:手改 lock 会导致依赖树不一致;删 lock 后版本可能漂移;node_modules 不提交。
从脚手架到业务目录:components 与 assets
为什么需要:脚手架只给最小骨架,轮播图等业务组件要另建目录。
怎么想:按组件聚合,一个组件一个目录,入口 index.js,样式 index.css,图片放 assets/images。
怎么做:转写稿中在 src 下建 components/Swiper,Swiper 下建 index.js、index.css;图片放 assets/images;组件里导入样式和图片,图片路径用 require 加模板字符串拼接。
易错点:相对路径层级容易错,转写稿里从 Swiper 返回两层到 src 再进 assets/images;图片名不要写死为 1、2、3,转写稿后面把图片放进数组、用下标管理,说明资源命名与索引要分离。样式不生效时,第一反应是检查类名是否拼错并刷新。同音字纠正:Swiper 不是 swepper/swipper,render 不是 runner,index 不是 inice/inex,“轮模图/轮波图”应理解为轮播图。
读目录的调试路径
为什么需要:出错时要知道先看哪一层。
怎么想:先看浏览器入口 public/index.html,再看 src/index.js 是否挂载成功,再看 App.js 组件树,最后看样式和资源路径。
怎么做:用 React 调试工具看组件结构和 state;改状态后视图重新渲染;样式问题看类名和样式文件导入;图片问题看 require 路径和文件名。
易错点:只看页面不查入口;状态直接赋值不会触发重新渲染,转写稿强调要用 setState;不要把“数据驱动视图”做成手动操作 DOM。
课堂画面






代码示例
示例一:局部安装 create-react-app 并启动项目
shell
# 1. 找一个目录【不能是中文】,打开终端
# 2. 初始化 package.json
npm init -y
# 3. 局部安装 create-react-app
npm i create-react-app
# 4. 通过局部命令创建项目(用 npx 调 node_modules/.bin 里的命令)
npx create-react-app 02-react-scaffold
# 5. 进入项目目录并启动项目
cd 02-react-scaffold
npm start关键行与易错点:
npm init -y先生成package.json,否则第 3 步装出来的包没有地方记录依赖,后面npx可能找不到。- 第 3 步是局部安装(不加
-g),所以第 4 步必须用npx,不能直接写create-react-app 项目名;npx会优先到node_modules/.bin里找这个命令。 - 项目目录不能是中文路径,否则安装和启动都容易报错。
- 工程目录结构要记住这几个:
public/index.html是网站入口 html,src/index.js是 js 入口,src/App.js是根组件,node_modules是 npm 包目录,package.json是包配置文件,package-lock.json是包版本锁文件。自己写的组件一律放在src下的components里,不要在根组件App.js里直接开发。
示例二:轮播图布局(结构 + 样式)
jsx
// src/components/Swiper/index.jsx
import React, { Component } from 'react'
import './index.css'
export default class Swiper extends Component {
state = {
index: 1
}
render() {
const { index } = this.state
return (
<div className="wrapper">
<img src={require(`../../assets/images/${index}.jpeg`)} alt="" />
<span className="leftBtn"><</span>
<span className="rightBtn">></span>
</div>
)
}
}css
/* src/components/Swiper/index.css */
.wrapper {
width: 600px;
height: 350px;
border: 3px solid red;
margin: 50px auto;
position: relative; /* 子元素绝对定位的参照物 */
}
.wrapper img {
width: 100%;
height: 100%;
}
.wrapper span {
display: block; /* span 默认是行内元素,必须转块 */
width: 30px;
height: 60px;
position: absolute;
top: 145px; /* (350 - 60) / 2 = 145,垂直居中 */
background: #ccc;
line-height: 60px; /* 让箭头在按钮内垂直居中 */
text-align: center; /* 水平居中 */
cursor: pointer; /* 小手 */
user-select: none; /* 双击不被选中 */
}
.rightBtn {
right: 0;
}关键行与易错点:
- 图片路径用
require(../../assets/images/${index}.jpeg)拼模板字符串。当前文件在src/components/Swiper/,所以要先退两层到src,再进assets/images。 <>是 HTML 实体,直接写<>会被当成标签解析。- 左右按钮要定位,父级
.wrapper必须写position: relative,否则top/right会跑到整个页面上去——这是最容易漏的一行。 - 样式没生效时,先检查类名有没有拼错,再刷新看一次(转写稿里就是类名写错导致“完全没作用”)。
- 在
App.js里用<Swiper />引入后图片会“过于壮硕”,一定要用.wrapper img { width:100%; height:100% }控制尺寸。
示例三:轮播图功能实现(数组版本,完整可运行)
jsx
// src/components/Swiper/index.jsx
import React, { Component } from 'react'
import './index.css'
export default class Swiper extends Component {
state = {
index: 0, // 下标从 0 开始
imgArr: ['1.jpeg', '2.jpeg', '3.jpeg'] // 图片名随便起,改数组就行
}
// 右侧按钮:下一张
rightBtnClick = () => {
let index = this.state.index
index++
if (index > this.state.imgArr.length - 1) { // 越界就回到第一张
index = 0
}
this.setState({ index })
}
// 左侧按钮:上一张
leftBtnClick = () => {
let index = this.state.index
index--
if (index < 0) { // 越界就回到最后一张
index = this.state.imgArr.length - 1
}
this.setState({ index })
}
render() {
const { index, imgArr } = this.state
return (
<div className="wrapper">
<img src={require(`../../assets/images/${imgArr[index]}`)} alt="" />
<span className="leftBtn" onClick={this.leftBtnClick}><</span>
<span className="rightBtn" onClick={this.rightBtnClick}>></span>
</div>
)
}
}jsx
// src/App.js
import React, { Component } from 'react'
import Swiper from './components/Swiper'
export default class App extends Component {
render() {
return (
<div>
<Swiper />
</div>
)
}
}关键行与易错点:
- 事件回调必须写成箭头函数(
rightBtnClick = () => {}),这样函数内部没有自己的this,会使用 render 里的this,即当前组件实例;写成普通函数function再onClick={this.rightBtnClick},this就是undefined,this.setState直接报错。 onClick后面只能放函数的引用地址,不能写onClick={this.rightBtnClick()},那样一渲染就立刻调用。- 修改状态一定用
this.setState({ index })。直接this.state.index = xxx能改数据但不会重新渲染——React 是数据驱动视图,改完数据视图才会跟着变。 - 边界判断是核心:
length - 1才是最后一个下标,> length - 1回 0;< 0回length - 1。写成> length或> 3硬编码,一旦图片增删就会错位。 - 换成数组后下标从 1 变 0,
index初始值和两个边界值都要同步改,漏改一处就会白屏。 - 补充(多类名):CSS 模块化时
className只能给一个值,多个类名要用数组加空格拼接:className={[styles.box, styles.app, 'global'].join(' ')}。
常见错误
npx create-react-app报“command not found”或版本对不上 现象:局部装完了create-react-app,npx却提示找不到命令 / 拉起了另一个旧版本。 原因:没在npm init -y的那个目录里执行;或本机全局也装过create-react-app,npx优先命中了全局那份。 修法:确认当前路径下有package.json与node_modules/.bin,在同一目录执行npx create-react-app 项目名;必要时npm uninstall -g create-react-app后再试。创建项目直接失败,提示项目名不合法 现象:
Could not create a project called "我的项目",或名字含大写、空格报错。 原因:脚手架要求项目名是小写、无中文、无空格的 npm 合法包名;路径里带中文也会让部分依赖安装异常。 修法:项目名改成my-app、react-demo这类;父目录也换成纯英文路径(如D:/code),再重新执行创建命令。CSS 模块化只写一个类名,多类名拼不出来 现象:元素要同时挂
styles.box、styles.app和全局类,直接写className={styles.box styles.app}或字符串相加,结果类名变成[object Object]或样式全丢。 原因:CSS Modules 导出的是对象,多个类名需要先收集再拼成一个字符串。 修法:放进数组再join(' '),例如className={[styles.box, styles.app, 'globalCls'].join(' ')};全局类不要走styles。轮播图点到最后一张再点就白屏 / 图片 404 现象:点“下一张”到第 3 张后再点,图裂了或报
Cannot find module。 原因:索引只做了自增没做回绕;同时图片路径把文件名和索引硬绑在一起(${index}.jpeg),文件名一改就崩。 修法:自增后判边界,超出末位下标就回到0;图片改用数组存放,渲染时取imgs[index],索引只当下标用。setState改了值但页面不动 现象:this.state.index = 2之后 console 里值变了,视图没变。 原因:直接赋值只改了对象属性,没有触发 React 的重新渲染流程。 修法:一律走this.setState({ index }),且先用局部变量算好边界再一次性 set。事件里
this是undefined,报“Cannot read properties of undefined (reading 'setState')” 现象:点击按钮报错,this打印出来是undefined。 原因:类组件方法默认不绑定this,作为回调传入时丢失了调用上下文。 修法:统一用箭头函数定义方法,或在 JSX 中包一层箭头函数调用,或在构造器里bind。onClick={this.handleClick()}一进页面就执行了 现象:还没点按钮,状态就变了 / 直接进入死循环。 原因:花括号里写的是函数调用,渲染时立即执行,把返回值交给了onClick。 修法:写引用onClick={this.handleClick},需要传参时再包一层onClick={() => this.handleClick(id)}。React 17 写法混进 18 项目,控制台一片 warning 现象:
ReactDOM.render仍能跑,但提示 “no longer supported”。 原因:装的是 18 的包,入口却沿用了 17 的react-dom直接渲染写法。 修法:18 用createRoot(document.getElementById('root')).render(<App />);若刻意演示 17,就别把这段留在正式入口里。
练习
从零走一遍局部安装流程:新建纯英文目录 →
npm init -y→ 局部装create-react-app→ 用npx创建项目 → 启动。中途故意把项目名写成中文,记录报错信息并说明原因。对照目录结构,说出
public/index.html与src/index.js各自的作用;然后把src下你认为本项目用不到的文件删掉,验证项目还能否正常启动,并解释reportWebVitals.js、setupTests.js删掉后有什么影响。在轮播图组件里加一个“跳转到指定张”的按钮组(1/2/3),要求索引仍受边界约束;再验证:把图片文件名全部改成
a.jpeg、b.jpeg、c.jpeg,代码需要改动几处?给轮播图写多类名样式:根容器同时应用 CSS Modules 的
wrapper、wrapperDark和一个全局类center,用一行className完成,并说明为什么不能直接字符串相加。用一句话分别描述挂载、更新、卸载三个阶段各自对应的一次“程序员可介入的时机”,并举出本项目里会触发“更新”阶段的两个具体操作。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | css模块化多个样式类名应用的补充说明 | 02:06 |
| 2 | 轮播图练习之布局 | 08:25 |
| 3 | 轮播图功能实现 | 04:36 |
| 4 | 轮播图-图片是数组元素版本 | 03:12 |
| 5 | react基础课件内容概览-react17语法介绍 | 12:46 |
| 6 | react生命周期的理解 | 07:32 |
小结
- 局部安装的核心是“在哪装、在哪用”:
package.json所在目录决定npx能不能找到本地命令,路径和项目名保持纯英文小写能省掉一大半玄学报错。 - 目录结构只需记住两条主线:
public是静态资源与页面入口,src是开发目录,index.js是 JS 入口、App.js是根组件。 - CSS Modules 导出的是对象,多类名必须自己拼成字符串——数组 +
join(' ')是最省心的写法,全局类不要混进styles。 - 轮播图这类需求,布局只做一次,功能全靠“改数据”;把索引当作唯一状态源,边界判断在 setState 之前完成。
- 图片路径与索引解耦(数组版)是这一节最有价值的重构:新增一张图只需往数组里加一项,逻辑代码零改动。
- 类组件里
this和setState是两大坑源:方法用箭头函数定义,状态只通过setState改,直接赋值必然视图不更新。 - 生命周期可以先用“出生—更新—死亡”的人生存节点来类比,先建立“到点执行特定方法”的直觉,再去看具体钩子名。
- React 17 与 18 的入口差别集中在
createRoot:包版本和写法要对齐,否则功能能跑但 warning 不断。