1 慢速上手
约 920 个字 144 行代码 预计阅读时间 5 分钟
使用 JS 直接操作 DOM 会导致大量的重绘重排
特点
- 组件化模式、声明式编码
- 可以在 React Native 中可以使用 React 语法进行移动端开发
- 使用 虚拟DOM + 优秀的 diff 算法,尽量减少和真实DOM的交互
1 Hello World
1.1 使用脚手架创建项目
- 需要的环境
- NodeJS & npm
- create-react-app,使用
npm install -g create-react-app
可以全局安装
- 创建项目
create-react-app prj_name
- 项目启动
npm start
,默认端口号为 3000
项目结构
%PUBLIC_URL% - 表示 public 根目录
- public 静态资源文件
- favicon.icon
- index.html
- manifest.json 应用加壳的配置文件(Web套皮原生)
- robots.txt 爬虫协议
- src
- App.css APP组件样式(在 App.js 中自行引入)
- App.js APP组件本体
- APP.test.js APP组件测试
- index.css 样式
- index.js 入口文件
这里面会引入 idx.css,如果要在 html 中加载要删去重复部分
- logo.svg logo图
- reportWebVitals.js 性能分析文件(需要 web-vitals 库)
Hello World 组件
- 编写入口文件
// 引入核心库 和 ReactDOM import React from 'react' import ReactDOM from 'react-dom' // 引入根组件 import App from './App.js' // 渲染到页面 ReactDOM.render(<App/>, document.getElementById('root')) // 在 React18 中,render 被更改为以下形式 import {createRoot} from 'react-dom/client' const root = createRoot(document.getElementById('root')) root.render(<App/>)
- 创建根组件 APP
样式模块化
用 less 写没有这个问题捏
- 样式表更名为
comp.module.css
- 使用
import comp_name from './comp.module.css'
- 渲染时返回 tag
className={comp_name.name}
1.2 单页面应用(CDN引入)
<body>
<div id="app"></div>
<script crossorigin
src="https://unpkg.com/react@16/umd/react.development.js">
</script>
<script crossorigin
src="https://unpkg.com/react-dom@16/umd/react-dom.development.js">
</script>
<script
src="https://unpkg.com/babel-standalone@6/babel.min.js">
</script>
<script type="text/babel">
// 必须指明内容为 babel
// 1. 创建虚拟DOM
const VDOM = <h1>Hello, world!</h1>
// 2. 将虚拟DOM渲染到指定容器中
ReactDOM.render(
VDOM,
document.getElementById('app')
);
</script>
</body>
2 虚拟 DOM
2.1 两种创建方式
- 使用 JSX 创建(就和上面一样)
- 使用 原生JS 创建
// 不用引入 babel 了捏 // React.createElement(标签名,属性,标签体内容) const VDOM = React.createElement('h1',{id:'title'}, 'hello world') ReactDOM.render( VDOM, document.getElementById('app') ); // JSX 下可以轻松实现的套娃 <h1><span></></>, JS 下要套函数 const VDOM = React.createElement('h1',{id:'title'}, React.createElement('span',{},'Hello World') )
2.2 vs 真实DOM
- 其实生成的 VDOM 只是一个普通的 Object 对象,比较轻量
- 最终会被转变为真实DOM,并渲染到页面上
3 JSX
是 JS 的扩展语法,类似于 XML
基础语法
- 定义虚拟DOM时,不要写引号
- 可以使用
{}
包裹潜入的 JS 表达式,如<h2 id={myId.toLowerCase()}>
- 指定样式得用
className="sth"
-> 用class
会爆炸(和ES6冲突) - 内联样式需要使用以下格式
- 只能有一个根标签(拿个
div
包一层) - 标签必须闭合(自闭合也可以)
- 小写开头的标签被认为是 html 元素,无同名元素则报错; 大写开头被认为是 React Component,组件没定义整个寄掉
列表渲染
const data = ['item1', 'item2', 'item2']
const VDOM = (
<div>
<ul>
{
data.map( (item, idx) => {
return <li key={idx}>{item}</li> // 单层括号,不是插值语法捏
})
}
</ul>
</div>
)
4 虚拟 DOM 与 Diff
- React / Vue 中的 key 有什么作用?(key 的内部原理是什么)
- Key 用于标识 虚拟DOM 对象,在 Update 时具有重要作用
- 当 state 中的数据变化时,React 会根据新的数据生成「新的虚拟DOM」,随后与「旧虚拟DOM」进行 diff
- 在「旧虚拟DOM」中找到具有相同 key 值的结构
- 内容不变:复用真实DOM
- 内容改变:生成新的真实DOM,并进行替换
- 未找到有相同 key 的结构 根据数据生成新的真实DOM,并渲染到页面
- 在「旧虚拟DOM」中找到具有相同 key 值的结构
- 为什么遍历列表时,key 最好不用 index?
- 若对数据进行:逆序添加、删除等破坏顺序的操作(一连串前后移位) 可能产生没有必要的真实DOM更新(\(O(n)\)),导致低效率
- 若结构中含有 input:可能产生错误的 DOM 更新
- 如果渲染的列表仅用于展示,使用key时没有问题的 最好选择记录的主键作为 key(保证 UNIQUE)
5 项目部署
- 打包捏
npm run build
生成build
- 使用 Serve
npm i serve -g
(全局安装)- 在主目录下执行
serve
命令(默认 port = 5000) 或者用serve 相对路径
- 如果我们把生成的文件丢到 cur_path 下就是
serve build
啦
- 在主目录下执行
6 其他
6.1 setState 的两种写法
-
对象式
setState(neoState, [callback])
回调函数
callback
将在render()
执行完毕后调用(类似于nextTick
) -
函数式
setState(updater, [callback])
updater
可以接收 state & props -
对象式的 setState 实际上是函数式的简写方式
- newState 依赖原状态时 —— 函数式
- newState 不依赖原状态时 —— 对象式
6.2 lazyLoad
针对路由组件
// 动态加载路由组件(路由组件代码会被分开打包)
const Login = lazy(() => import('@/pages/Login'));
// 使用 <Suspense> 指定在加载完成前的 Loading 界面
import {Suspense} from 'react'; // 包裹所有注册路由<Route>
<Suspense fallback={<h1>Loading</h1>}>
<Switch>
<Router path="..." component={Login}/>
<Redirect to="/home"/>
</Switch>
</Suspense>
其实 suspense 显示的内容也可以是组件(但是不能再 lazy 了)
6.3 Hook
使得函数式组件可以使用 state 及 其他React特性
useState
// 由于 rfc 没有 this,绑定 onClick={funcName} 即可
// 定义回调使用 function funcName(params){} 即可
export default function Demo {
// 分别是 [cur_val, set_val_method]
const [count, setCount] = React.useState(initVal)
// 可以使用 {count} 读取,使用 setCount(newVal/count+1) 重置
// setCount( val => new_val) new_val 可以基于旧值迭代
}
useEffect
支持使用 LifeCycle
export default function Demo {
// 不使用第二个参数时 = DidMount + DidUpdate(检测所有数据)
// 第二个参数为[] = DidMount (不检测任何数据)
// 第二个参数为[count] = DidMount + UpdateCount
// 也可以用,分隔多个检测对象
React.useEffect(()=>{
let timer = setInterval(()=>{
setCount(count => count+1)
}, 1000)
return () => { // 返回的函数相当于 WillUnmount
clearInterval(timer)
}
},[]) // 此时 = DidMount
}
useRef
export default function Demo() {
const myRef = React.useRef()
render() {
return (
<input ref={myRef}/>
)
}
// 可以用 myRef.current.value 拿到当前的输入值
}
6.4 Fragment
充当 render 的「虚拟」根标签,类似于 Vue 中的
<template>
- 可以指定 key
<Fragement key={1}>
- 其实也可以使用空标签
<>
,但不能指定 key