- 是一个用于构建用户界面的 javascript 库
1 hello world
- 需要安装两个包:
npm install react react-dom
- 要支持 jsx 语法,需要 react
- 要支持 dom 渲染,需要 react-dom
import React from "react";
import { createRoot } from "react-dom/client";
const ele = <h1>hello world</h1>;
const root = createRoot(document.getElementById("app"));
root.render(ele);
2 JSX
- jsx 语法不是字符串也不是 html
- 更偏向 js,具有 js 的完整功能
- 最外层如果是一个元素可以直接写
- 最外层如果是多一个元素要写在数组里面,最外层的数组外面不用写{}
- 标签的属性名需要小驼峰命名,属性 class 要写成 className
- 如果属性值是字符串用引号包括,如果是 js 表达式用{}, 引号和{}不能同时使用
- Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用,最后会生成一个 react 元素
- 具体代码见:
src/12k/01learnJsx.jsx
3 类组件和函数组件
- 书写组件最简单的方法是函数组件
- 类组件里面有 this 是组件实例对象
- 函数组件里面没有 this,this 是 undefined
- 自定义组件名要求首字母驼峰命名
- 类组件语法 - 利用 es6 的 class 语法
- 具体代码见:
src/12k/02learnComponent.jsx
class MyHome extends React.Component {
render() {
}
}
function MyFooter() {
}
4 props
- 自定义组件可以当标签使用,标签上都有属性
- 书写标签上的任意属性,在自定义组件内部可以通过 props 接收
- 书写标签内的元素,在自定义组件内容可以通过 props 接收
- 类组件通过: this.props 接收,其中标签内的元素通过 this.props.children 接收
- 函数组件通过: 函数的第一个形参接收 props,标签内的元素通过 props.children 接收
- 自定义组件和原生的 dom 元素的区别
- 原生的 dom 元素是小写的,自定义组件是首字母大写的
- 原生的标签上的属性值最后一定是字符串,自定义组件上的属性值可以是任意类型
- 自定义组件上的属性如果只写属性不写值,表示值是 true
- 在自定义组件内部不要修改 props,是只读的
- 具体代码见:
src/12k/03learnProps.jsx
5 元素渲染
- 一个 react 元素一旦创建,就不可变
- 如果要更新一个 react 元素
- 目前只能重新创建一个新的 react 元素
- 然后渲染到根节点中
- 渲染 dom 元素的时候,会把本次 react 元素对象和上一次的 react 元素对象对比
- 只更新必要的部分
- 具体代码见:
src/main.jsx
的第四个部分
6 props 类型检查
- 官方推荐: prop-types
- 在 v15.5 之前包含在 react 里面,后面独立成一个库:prop-types
- 安装:
npm install prop-types -S
- 使用
import PropTypes from "prop-types";
import React from "react";
class MyHome extends React.Component {
render() {}
}
MyHome.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
love: PropTypes.array,
fn: PropTypes.func,
bool: PropTypes.bool,
obj: PropTypes.object,
node: PropTypes.node,
element: PropTypes.element,
message: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
gender: PropTypes.oneOf(["男", "女"]),
};
MyHome.defaultProps = {
name: "lucy",
};
const ele = <MyHome name="lily" {...}/>;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
7 在 jsx 语法中书写注释
- jsx 是偏 js,所以注释是 js 代码,也要写在{}里面
- 最后注释就下面这个样子,不能使用 双斜杠
conse ele = <div>
{}
hello world
<div>
8 回忆原生 js 中的 this 指向问题
- 如果是事件处理函数调用,里面的 this 是事件源,里面的 event 是事件对象
- 如果是普通函数调用,里面的 this 非严格模式是 window,严格模式是 undefined
- 如果是对象的方法调用,里面的 this 是当前调用方法的对象
- 如何改变 this 的指向
- fn(实参 1,实参 2,…) - 没有改变 this 的指向
- fn.call(改变成的 this 指向,实参 1,实参 2,…) - 只改变本此调用的 this
- fn.apply(改变成的 this 指向,[实参 1,实参 2,…]) - 只改变本此调用的 this
- let newFn = fn.bind(改变成的 this 指向) - 不调用函数,生成一个新的函数 newFn,newFn 不管怎么调用 this 都固定
9 state - 类似 vue 里面的 data
- state 只能在构造函数中初始化赋值
- state 不能直接修改,要通过 setState 修改
- setState 的更新可能是异步的
- setState 的更新会进行浅合并,只合并同一个函数域
- 如果 setState 修改的值依赖上一次的值,setState 传入的参数就必须是一个函数
- 语法 1: 不依赖上一次的值
this.setState(
{
num: 100,
},
() => {
console.log(this.state);
}
);
this.setState((state) => {
return {
num: state.num + 1,
};
});
10 组件生命周期 - lifeCycle
- 组件生命周期分为 3 大部分
- 挂载时:
constructor
,render
,componentDidMount
- 更新时:
shouldComponentUpdate
,render
,componentDidUpdate
- 卸载时:
componentWillUnmount
- constructor
- 构造函数,第一行:
super()
- 初始化组件状态(初始化声明式变量):
this.state = {count:1,num:100}
- 构造函数中可以直接 state 赋值,不要写 setState
- 不要在构造函数中开定时器,不要调用接口
- 挂载时: render
- 挂载时必定执行一次 render,不能通过 shouldComponentUpdate 阻止
- render 是类组件必须的一个生命周期
- 返回值是视图结构(JSX,Fiber 树,React 元素)
- 可以做数据处理,一般是解构一些数据
- 不调用接口,不开启定时器,不要写 setState
- componentDidMount
- react 元素变成了 dom
- 此时数据变成了真实的 dom
- 可以初始化一些需要 dom 的操作,可以开启定时器,可以请求接口
- 更新时:render
- 如果有 new Props,setState,forceUpdate,会触发 render 函数
- 但是:
- forceUpdate 一定触发 render 函数
- new Props,setState 在触发 render 函数之前会进过 shouldComponentUpdate
- 如果不写 shouldComponentUpdate,就必然进入 render
- shouldComponentUpdate - react 的性能优化点
- 有两个参数:
- 第一个参数是:nextProps
- 第二个参数是:nextState
- 返回值是布尔值,如果是 true,就进入 render,如果是 false,就不进入 render
- 我们理想中的 shouldComponentUpdate 应该是,props 或者 state 有变化才进入 render,如果没有变化就不进入 render
- 我们自己写难度大,所有可以继承 React.PureComponent,他帮我们实现了基本的 shouldComponentUpdate 的上述功能
- componentDidUpdate
- 有两个参数:
- 第一个参数是:prevProps
- 第二个参数是:prevState
- 新的 dom 节点,更新完成,依赖新的 dom 的操作可以写在这里
- 不要写定时器,不要调用接口,不要写 setState
- componentWillUnmount
- 组件卸载的时候调用的函数
- 可以在里面进行清理操作,一般是:清除定时器,取消请求等
11 事件处理
- react 的事件处理和 dom 的事件处理基本一致
- react 的事件名称用小驼峰命名
- react 的事件处理函数就是一个函数,不是字符串
- 事件处理函数没有绑定 this,需要我们自己绑定 this
class MyHome extends React.PureComponent {
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
}
clickHandler(e) {
console.log(this);
console.log(e);
}
render() {
return (
<div>
<h1 onClick={this.clickHandler}>this是undefined,e是合成事件</h1>
<h1
onClick={(e) => {
this.clickHandler(e);
}}
>
this是组件实例对象,需要在箭头函数中获取合成事件,作为实参传递下去
</h1>
<h1 onClick={this.clickHandler}>this是组件实例对象,也有事件对象传入</h1>
</div>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
12 非受控组件 - 不推荐
- 没有设置表达的受控属性的值,就是非手动组件
- 非受控组件通过 dom 操作来获取表单元素的值
13 受控组件 - 推荐
- 设置了受控属性的值,就是受控组件
- 可以受控的表单属性主要有
- value: text,password,color,select,…
- checked: radio,checkbox
- 受控组件通过设置受控属性的值,和 onChange 事件来设置和获取表单元素的值
- 如果手动属性是 value,我们一般写成:
class MyForm extends React.PureComponent {
constuctor() {
super();
this.state = {
name: "",
};
}
changeHandler(e, key) {
this.setState({
[key]: e.target.value,
});
}
render() {
const { name } = this.state;
return (
<div>
<input
type="text"
value={name}
onChange={(e) => this.changeHandler(e, "name")}
/>
</div>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 如果受控的属性是 radio 的 checked,但是我们需要的数据是 value
class MyForm extends React.PureComponent {
constuctor() {
super();
this.state = {
gender: "man",
};
}
changeHandler(e, key) {
this.setState({
[key]: e.target.value,
});
}
render() {
const { gender } = this.state;
return (
<div>
<input
type="radio"
value="man"
checked={gender == "man"}
onChange={(e) => this.changeHandler(e, "gender")}
/>
男
<input
type="radio"
value="woman"
checked={gender == "woman"}
onChange={(e) => this.changeHandler(e, "gender")}
/>
女
</div>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 如果受控的属性是 checkbox 的 checked,但是我们需要的数据是 value 的集合
class MyForm extends React.PureComponent {
constuctor() {
super();
this.state = {
love: ["football", "basketball"],
};
}
changeHandler(e,key) {
const {value,checked} = e.target
this.setState((state)=>{
[key]:checked?[...state.love,value]:state.love.filter(item=>item!=value)
})
}
render() {
const { love } = this.state;
return (
<div>
<input
type="checkbox"
value="football"
checked={love.includes("football")}
onChange={(e) => this.changeHandler(e, "love")}
/>
足球
<input
type="checkbox"
value="basketball"
checked={love.includes("basketball")}
onChange={(e) => this.changeHandler(e, "love")}
/>
篮球
<input
type="checkbox"
value="waterfall"
checked={love.includes("waterfall")}
onChange={(e) => this.changeHandler(e, "love")}
/>
水球
</div>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
14 状态提升
- 如果多个组件之间要共享状态,把状态提升到共同的父组件中声明
- 如果要在子组件中修改父组件的状态,需要父组件通过 props 传递修改状态的方法给子组件,传递方法的时候要 bind 父组件的 this
class Input extends React.PureComponent(){
changeFatherState(){
const {changeList} = this.props
}
render(){
return ()
}
}
class List extends React.PureComponent(){
render(){
const {data} = this.props
return ()
}
}
class Todos extends React.PureComponent(){
constructor(){
super()
this.state = {
list:[]
}
}
changeList(){
}
render(){
return <>
<Input changeList={this.changeList.bind(this)}/>
<List data={list}/>
</>
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
15 context
- 可以通过 React.createContext 创建一个上下文对象,比如:MyContext
- 语法:
React.createContext(上下文对象的默认值)
- 任意类组件可以通过: 组件名.contextType = MyContext,来使用 MyContext 这个上下文对象
- 在类组件内部通过:this.context 来获取上下文对象
- 如果前面的祖先没有通过 value 来给 MyContext 赋值,我就用 MyContext 的默认值
- 可以通过
MyContext.Provider
组件的 value 属性来给 MyContext 赋值
- 如果有多个 MyContext.Provider 设置 value 属性,使用最近的
16 Portal
- 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案
- 就是使用 react-dom 包里面的 createPortal 方法
- 语法:createPortal(react 元素,要插入到的 dom 节点)
17 Refs&DOM
- refs 提供了一种获取 dom 节点和 react 元素的方法
- 方法 1:
React.createRef()
class MyHome extends React.PureComponent {
constructor() {
super();
this.myRef = React.createRef();
}
componentDidMount() {
}
render() {
return (
<div>
{}
<input type="text" ref={this.myRef} />
{}
</div>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 方法 2: 回调 refs - 即可以绑定也可以解绑
class MyHome extends React.PureComponent {
constructor() {
super();
this.handler = (val)=>{
this.myInput = val;
}
}
render() {
return (
<div>
{}
<input type="text" ref={} />
</div>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
18 refs 转发
- 将 ref 自动地通过组件传递到其一子组件的技巧
- 重用的组件库是很有用的
- 一般组件库里面的哪些组件会有 refs 转发:Input,Button,…
- 这些组件倾向于以一种类似常规 DOM button 和 input 的方式被使用,并且访问其 DOM 节点
const Input = React.forwardRef((props, ref) => {
return <input type="text" {...props} ref={ref} />;
});
class MyHome extends React.PureComponent {
constructor() {
super();
this.input = React.createRef();
}
componentDidMount() {
this.input.current.focus();
}
render() {
return (
<div>
<h1>MyHome</h1>
<Input ref={this.input} />
</div>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
19 高阶组件
- 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧
- 高阶组件: 是一个函数,参数是一个组件,返回值也是一个组件
- 高阶组件不是组件时函数
- 通过高阶组件可以给组件添加一些额外的信息
20 hooks
const [count, setCount] = useState(0);
setCount(10);
setCount((count) => count + 10);
useEffect(() => {
return () => {
};
}, [count]);
const double = useMemo(() => {
return count * 2;
}, [count]);
const fn = useCallback(() => {
console.log(count);
}, [count]);
const myRef = useRef("inp");
const myContext = useContext(ThemeContext);