# React简介
**react是什么?**
React用于构建用户界面的JS库。是一个将数据渲染为HTML视图的开源JS库。**
1.原生JS操作DOM繁琐,效率低
2.使用JS直接操作DOM,浏览器会进行大量的重绘重排
3.原生JS没有组件化编码方案,代码复用低
## **React有什么特点**
1- 声明式UI(JSX)
写UI就和写普通的HTML一样,抛弃命令式的繁琐实现
![img](https://cdn.nlark.com/yuque/0/2022/png/274425/1654489480461-0cfa5cac-eb47-4629-8f11-a7ca1d8c0227.png)
## 2- 组件化
组件是react中最重要的内容,组件可以通过搭积木的方式拼成一个完整的页面,通过组件的抽象可以增加复用能力和提高可维护性
![img](https://cdn.nlark.com/yuque/0/2022/png/274425/1659285398963-681a495b-1347-4a9f-bc62-1be28ed805eb.png)
# 环境初始化
## 1. 使用脚手架创建项目
- 打开命令行窗口
- 执行命令
```bash
$ npx create-react-app react-basic
```
说明:
1. 1. npx create-react-app 是固定命令,`create-react-app`是React脚手架的名称
2. react-basic表示项目名称,可以自定义,保持语义化
3. npx 命令会帮助我们临时安装create-react-app包,然后初始化项目完成之后会自自动删掉,所以不需要全局安装create-react-app
- 启动项目
```bash
$ yarn start
or
$ npm start
```
## 2. 项目目录说明调整
- 目录说明
1. 1. `src` 目录是我们写代码进行项目开发的目录
2. `package.json` 中俩个核心库:react 、react-dom
- 目录调整
1. 1. 删除src目录下自带的所有文件,只保留app.js根组件和index.js
2. 创建index.js文件作为项目的入口文件,在这个文件中书写react代码即可
- 入口文件说明
```jsx
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
// 引入根组件App
import App from './App'
// 通过调用ReactDOM的render方法渲染App根组件到id为root的dom节点上
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
```
JSX基础
## 1. JSX介绍
概念:JSX是 JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构
作用:在React中创建HTML结构(页面UI结构)
优势:
1. 采用类似于HTML的语法,降低学习成本,会HTML就会JSX
2. 充分利用JS自身的可编程能力创建HTML结构
注意:JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 [@babel/plugin-transform-react-jsx](@babel/plugin-transform-react-jsx) 包,用来解析该语法
![img](https://cdn.nlark.com/yuque/0/2022/png/274425/1654489661908-d354840e-78b8-43ad-a882-8129742c794e.png)
## 2. JSX中使用js表达式
**语法**
```
{ JS 表达式 }
const name = '柴柴'
<h1>你好,我叫{name}</h1> // <h1>你好,我叫柴柴</h1>
```
**可以使用的表达式**
1. 字符串、数值、布尔值、null、undefined、object( [] / {} )
2. 1 + 2、'abc'.split('')、['a', 'b'].join('-')
3. fn()
**特别注意**
if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 `{}` 中!!
## 3. JSX列表渲染
页面的构建离不开重复的列表结构,比如歌曲列表,商品列表等,我们知道vue中用的是v-for,react这边如何实现呢?
实现:使用数组的`map` 方法
```jsx
// 来个列表
const songs = [
{ id: 1, name: '痴心绝对' },
{ id: 2, name: '像我这样的人' },
{ id: 3, name: '南山南' }
]
function App() {
return (
<div className="App">
<ul>
{
songs.map(item => <li>{item.name}</li>)
}
</ul>
</div>
)
}
export default App
```
注意点:需要为遍历项添加 `key` 属性
![img](https://cdn.nlark.com/yuque/0/2022/png/274425/1654489746660-d500357d-1e62-4016-a25f-d36594fdfead.png)
1. key 在 HTML 结构中是看不到的,是 React 内部用来进行性能优化时使用
2. key 在当前列表中要唯一的字符串或者数值(String/Number)
3. 如果列表中有像 id 这种的唯一值,就用 id 来作为 key 值
4. 如果列表中没有像 id 这种的唯一值,就可以使用 index(下标)来作为 key 值
## 4. JSX条件渲染
作用:根据是否满足条件生成HTML结构,比如Loading效果
实现:可以使用 `三元运算符` 或 `逻辑与(&&)运算符`
```jsx
// 来个布尔值
const flag = true
function App() {
return (
<div className="App">
{/* 条件渲染字符串 */}
{flag ? 'react真有趣' : 'vue真有趣'}
{/* 条件渲染标签/组件 */}
{flag ? <span>this is span</span> : null}
</div>
)
}
export default App
```
## 5. JSX样式处理
- 行内样式 - style
```jsx
function App() {
return (
<div className="App">
<div style={{ color: 'red' }}>this is a div</div>
</div>
)
}
export default App
```
- 行内样式 - style - 更优写法
```jsx
const styleObj = {
color:red
}
function App() {
return (
<div className="App">
<div style={ styleObj }>this is a div</div>
</div>
)
}
export default App
```
- 类名 - className(推荐)
```css
.title {
font-size: 30px;
color: blue;
}
```
## 6. JSX注意事项
1. JSX必须有一个根节点,如果没有根节点,可以使用`<></>`(幽灵节点)替代
2. 所有标签必须形成闭合,成对闭合或者自闭合都可以
3. JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 `class -> className` `for -> htmlFor`
4. JSX支持多行(换行),如果需要换行,需使用`()` 包裹,防止bug出现
## 函数组件
**概念**
使用 JS 的函数(或箭头函数)创建的组件,就叫做`函数组件`
**组件定义与渲染**
```jsx
// 定义函数组件
function HelloFn () {
return <div>这是我的第一个函数组件!</div>
}
// 定义类组件
function App () {
return (
<div className="App">
{/* 渲染函数组件 */}
<HelloFn />
<HelloFn></HelloFn>
</div>
)
}
export default App**
```
1. 组件的名称**必须首字母大写**,react内部会根据这个来判断是组件还是普通的HTML标签
2. 函数组件**必须有返回值**,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null
3. 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的**返回值**就是对应的内容
4. 使用函数名称作为组件标签名称,可以成对出现也可以自闭合
5. ## 函数组件的事件绑定
`目标任务:` 能够独立绑定任何事件并能获取到事件对象e
### 1. 如何绑定事件
- 语法
on + 事件名称 = { 事件处理程序 } ,比如:`<div onClick={ onClick }></div>`
- 注意点
react事件采用驼峰命名法,比如:onMouseEnter、onFocus
- 样例
```jsx
// 函数组件
function HelloFn () {
// 定义事件回调函数
const clickHandler = () => {
console.log('事件被触发了')
}
return (
// 绑定事件
<button onClick={clickHandler}>click me!</button>
)
}
```
### 2. 获取事件对象
获取事件对象e只需要在 事件的回调函数中 补充一个形参e即可拿到
```jsx
// 函数组件
function HelloFn () {
// 定义事件回调函数
const clickHandler = (e) => {
console.log('事件被触发了', e)
}
return (
// 绑定事件
<button onClick={clickHandler}>click me!</button>
)
}
```
### 3. 传递额外参数
解决思路: 改造事件绑定为箭头函数 在箭头函数中完成参数的传递
```jsx
import React from "react"
// 如何获取额外的参数?
// onClick={ onDel } -> onClick={ () => onDel(id) }
// 注意: 一定不要在模板中写出函数调用的代码 onClick = { onDel(id) } bad!!!!!!
const TestComponent = () => {
const list = [
{
id: 1001,
name: 'react'
},
{
id: 1002,
name: 'vue'
}
]
const onDel = (e, id) => {
console.log(e, id)
}
return (
<ul>
{list.map(item =>(
<li key={item.id}>
{item.name}
<button onClick={(e) => onDel(e, item.id)}>x</button>
</li>
))}
</ul>
)
}
function App () {
return (
<div>
<TestComponent />
</div>
)
}
export default App
```
# React组件通信
## 组件通信的意义
组件是独立且封闭的单元,默认情况下组件**只能使用自己的数据(state)**
组件化开发的过程中,完整的功能会拆分多个组件,在这个过程中不可避免的需要互相传递一些数据
为了能让各组件之间可以进行互相沟通,数据传递,这个过程就是组件通信
## 父传子实现
**实现步骤**
1. 父组件提供要传递的数据 - `state`
2. 给子组件标签`添加属性`值为 state中的数据
3. 子组件中通过 `props` 接收父组件中传过来的数据
1. 1. 类组件使用this.props获取props对象
2. 函数式组件直接通过参数获取props对象
![img](https://cdn.nlark.com/yuque/0/2022/png/274425/1654490432739-ea283505-3ddd-4403-9fba-7735b04b451e.png)
**代码实现**
```jsx
import React from 'react'
// 函数式子组件
function FSon(props) {
console.log(props)
return (
<div>
子组件1
{props.msg}
</div>
)
}
// 类子组件
class CSon extends React.Component {
render() {
return (
<div>
子组件2
{this.props.msg}
</div>
)
}
}
// 父组件
class App extends React.Component {
state = {
message: 'this is message'
}
render() {
return (
<div>
<div>父组件</div>
<FSon msg={this.state.message} />
<CSon msg={this.state.message} />
</div>
)
}
}
export default App
```
## props说明
**1. props是只读对象(readonly)**
根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
**2. props可以传递任意数据**
数字、字符串、布尔值、数组、对象、`函数、JSX`
```jsx
class App extends React.Component {
state = {
message: 'this is message'
}
render() {
return (
<div>
<div>父组件</div>
<FSon
msg={this.state.message}
age={20}
isMan={true}
cb={() => { console.log(1) }}
child={<span>this is child</span>}
/>
<CSon msg={this.state.message} />
</div>
)
}
}
```
![img](https://cdn.nlark.com/yuque/0/2022/png/274425/1654490465147-2322a19d-104f-438d-a017-2725073ec0d7.png)
## 子传父
**实现步骤**
1. 父组件提供一个回调函数 - 用于接收数据
2. 将函数作为属性的值,传给子组件
3. 子组件通过props调用 回调函数
4. 将子组件中的数据作为参数传递给回调函数
![img](https://cdn.nlark.com/yuque/0/2022/png/274425/1654490502446-0596a169-847f-4446-91ce-a9a0237a9074.png)
**代码实现**
```jsx
import React from 'react'
// 子组件
function Son(props) {
function handleClick() {
// 调用父组件传递过来的回调函数 并注入参数
props.changeMsg('this is newMessage')
}
return (
<div>
{props.msg}
<button onClick={handleClick}>change</button>
</div>
)
}
class App extends React.Component {
state = {
message: 'this is message'
}
// 提供回调函数
changeMessage = (newMsg) => {
console.log('子组件传过来的数据:',newMsg)
this.setState({
message: newMsg
})
}
render() {
return (
<div>
<div>父组件</div>
<Son
msg={this.state.message}
// 传递给子组件
changeMsg={this.changeMessage}
/>
</div>
)
}
}
export default App
```
# Hooks基础
Hooks的本质:**一套能够使函数组件更强大,更灵活的“钩子”**
## useState
### 1. 基础使用
**作用**
useState为函数组件提供状态(state)
**使用步骤**
1. 导入 `useState` 函数
2. 调用 `useState` 函数,并传入状态的初始值
3. 从`useState`函数的返回值中,拿到状态和修改状态的方法
4. 在JSX中展示状态
5. 调用修改状态的方法更新状态
**代码实现**
```jsx
import { useState } from 'react'
function App() {
// 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
// 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
const [count, setCount] = useState(0)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
```
### 2. 状态的读取和修改
**读取状态**
该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
**修改状态**
1. setCount是一个函数,参数表示`最新的状态值`
2. 调用该函数后,将使用新值替换旧值
3. 修改状态后,由于状态发生变化,会引起视图变化
**注意事项**
1. 修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型
### 3. 组件的更新过程
`本节任务:` 能够理解使用hook之后组件的更新情况
函数组件使用 **useState** hook 后的执行过程,以及状态值的变化
- 组件第一次渲染
1. 1. 从头开始执行该组件中的代码逻辑
2. 调用 `useState(0)` 将传入的参数作为状态初始值,即:0
3. 渲染组件,此时,获取到的状态 count 值为: 0
- 组件第二次渲染
1. 1. 点击按钮,调用 `setCount(count + 1)` 修改状态,因为状态发生改变,所以,该组件会重新渲染
2. 组件重新渲染时,会再次执行该组件中的代码逻辑
3. 再次调用 `useState(0)`,此时 **React 内部会拿到最新的状态值而非初始值**,比如,该案例中最新的状态值为 1
4. 再次渲染组件,此时,获取到的状态 count 值为:1
注意:**useState 的初始值(参数)只会在组件第一次渲染时生效**。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值
```jsx
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
// 在这里可以进行打印测试
console.log(count)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
```
### 4. 使用规则
1. `useState` 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
```javascript
function List(){
// 以字符串为初始值
const [name, setName] = useState('cp')
// 以数组为初始值
const [list,setList] = useState([])
}
```
1. `useState` 注意事项
1. 1. 只能出现在函数组件或者其他hook函数中
2. 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)
```javascript
let num = 1
function List(){
num++
if(num / 2 === 0){
const [name, setName] = useState('cp')
}
const [list,setList] = useState([])
}
// 俩个hook的顺序不是固定的,这是不可以的!!!
```
1.
## useEffect
**使用步骤**
1. 导入 `useEffect` 函数
2. 调用 `useEffect` 函数,并传入回调函数
3. 在回调函数中编写副作用处理(dom操作)
4. 修改数据状态
5. 检测副作用是否生效
**代码实现**
```jsx
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
useEffect(()=>{
// dom操作
document.title = `当前已点击了${count}次`
})
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
```
### 3. 依赖项控制执行时机
**1. 不添加依赖项**
组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
1. 组件初始渲染
2. 组件更新 (不管是哪个状态引起的更新)
```jsx
useEffect(()=>{
console.log('副作用执行了')
})
```
**2. 添加空数组**
组件只在首次渲染时执行一次
```jsx
useEffect(()=>{
console.log('副作用执行了')
},[])
```
**3. 添加特定依赖项**
副作用函数在首次渲染时执行,在依赖项发生变化时重新执行
```jsx
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('zs')
useEffect(() => {
console.log('副作用执行了')
}, [count])
return (
<>
<button onClick={() => { setCount(count + 1) }}>{count}</button>
<button onClick={() => { setName('cp') }}>{name}</button>
</>
)
}
```
**注意事项**
useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
## useContext
**实现步骤**
1. 使用`createContext` 创建Context对象
2. 在顶层组件通过`Provider` 提供数据
3. 在底层组件通过`useContext`函数获取数据
**代码实现**
```jsx
import { createContext, useContext } from 'react'
// 创建Context对象
const Context = createContext()
function Foo() {
return <div>Foo <Bar/></div>
}
function Bar() {
// 底层组件通过useContext函数获取数据
const name = useContext(Context)
return <div>Bar {name}</div>
}
function App() {
return (
// 顶层组件通过Provider 提供数据
<Context.Provider value={'this is name'}>
<div><Foo/></div>
</Context.Provider>
)
}
export default App
```
##