组件记住被交互过(state)
不使用hook
- 在组件的构造函数上设置this.state来初始化state(构造函数必搭配super(props))
1
2
3
4
5
6constructor(props) {
super(props)
this.state = {
value: null,
}
} - 将组件中要记住的值用this.props.xxx替换为this.state.xxx
- 将对应的监听事件改为
onClick={() => this.setState({value: 'X'})}
💚使用hook
1 | const [state, setState] = useState(null) |
不可变性!!!
- 用新数据替换旧数据(仍保留原数据)
- 时间旅行(撤销和恢复)
- 跟踪数据的改变
- 创建pure components,可确定不可变数据是否改变,从而确定何时重新渲染
时间旅行
- slice()保存副本到history — 保存历史记录
- 将state提升到顶层,把原本的state改为props
- map() — (已经记录完)展示历史步骤记录
JSX
- 语法:
<标签>文本,{变量/函数}</标签>
,与js混合用 - 字符串字面量用””包裹,表达式用{}
- JSX防止注入攻击
- JSX转译为React.createElement()函数调用
元素渲染
- 元素是构成 React 应用的最小砖块
- React元素是不可变元素
组件 & props
函数组件
- 只包含一个render方法并且不包含state时使用
1
2
3function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
class组件
- 函数组件与class组件在React中是等效
- 渲染组件(用户自定义组件) — 组件名称必须大写
- 组合组件: 在一个组件中使用组件
- 提取组件: 拆分代码,形成组合组件套娃模式
1
2
3
4
5class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
State
- state被称为局部或封装的原因:
- 除了拥有并设置了它的组件,其他组件都无法访问和知道某个组件是什么状态
- ❌不能直接修改state
- √ 使用this.setState,构造函数是唯一可以给state赋值的
- ❌this.props 和 this.state 可能会异步更新
- √ 让setState()接收一个函数而不是一个对象
1
2
3this.setState((state, props) => {
counter: state.counter + props.increment
})
- √ 让setState()接收一个函数而不是一个对象
- state的更新会被合并: 使用setState会浅合并
- 数据是向下流动的:
- 组件可以选择把state作为props向下传递给它的子组件,但子组件不知道它来自的是父组件的state还是props,还是手动输入,这通常被叫做『自上而下』或是『单向』的数据流.
浅合并 & 深合并
- 浅合并: 有多层嵌套的情况下,第一层合并且替换,第二层及以下直接替换不合并
- 深合并: 有多层嵌套的情况下,每一层合并且替换
事件处理
与DOM 元素事件处理不同
- 命名采用小驼峰式(aaaBaa)
- 传入一个函数作为事件处理函数 onClick={ 函数名 }
- 监听器:初始渲染时添加
- 不能通过返回false的方式『阻止默认行为必须显式使用preventDefault』
1
2
3
4
5
6function ActionLink() {
function handleClick(e) {
e.preventDefault() // 阻止默认事件
}
return <a href="#" onClick={handleClick}>Click me</a>
} - 像onClick={this.handleClick}这种形式,需要绑定this,否则this为undefined
- 解决方案1: 💚使用箭头函数
1
onClick={() => this.handleClick()}
- 解决方案2: 在constructor中添加
1
this.handleClick = this.handleClick.bind(this)
- 解决方案3: 使用实验性的class fields语法
1
2
3handleClick = () => {
console.log('this is:', this)
}
- 解决方案1: 💚使用箭头函数
向事件处理程序传递参数
- 显式:
1
<button onClick={(e) => this.deleteRow(id, e)}>Delete</button>
- 隐式:
1
<button onClick={this.deleteRow.bind(this, id)}>Delete</button>
条件渲染 (if 元素变量 || && || 三目运算符)
1 | <div> |
- 阻止组件的渲染(隐藏组件) — 在想要隐藏的组件中判断,直接 return null
列表 & Key
渲染多个组件
1 | const listItems = numbers.map((number) => |
基础列表组件
- 将上面的方法改成一个组件,直接调用该组件
- key: 唯一标识,在diff算法执行时更快找到对应的节点
- key在数组的上下文中被指定,只是兄弟节点之间必须唯一(两个不同数组可以使用相同key)
- key会传递给React,不会传递给组件.需要key属性的值,要用其它属性名显式传递
- JSX允许大括号中前任任何表达式,可以内联map()返回的结果
受控组件 | 非受控组件
- 受控组件:将状态变化交由React处理的组件,通过props获取其当前值,并通过回调函数通知变化
- 非受控组件: 通过ref引用获取的组件,使用DOM节点处理(ref)
表单(受控组件)
- 表单状态发生变化时,都会通知React,通过使用 setState()来更新
- textarea 标签(多行文本控件) — 与input用法相同
- select标签(下拉列表标签)
- 单选:通过value来设置默认选中
1
<select value={this.state.value}>
- 多选:
1
<select multiple={true} value={['B', 'C']}>`
- 单选:通过value来设置默认选中
- input标签
- input的变化没有更新节点setState,而它的变化是本身具有的功能,所以为非受控组件
- 处理多个input 添加name属性 根据event.target.name 的值选择要执行的操作
- 计算属性名称更新给定输入名称对应的state
1
this.setState({ [name]: value });
- 受控输入空值,可能将value指定为undefined或null
状态提升
- 将组件的共享状态提升到最近的共同父组件
- 把子组件的state数据放在它们共同的父组件中保存,然后父组件可以通过props将状态数据传给子组件中
- 组件通过this.props.xxx来接收
组合 vs 继承
- 包含关系 — 一个组件包含在另一个组件中
- 特例关系 — 组合组件,定制渲染
- 现在有一个组件,这个组件又包含在另一个组件,并且值都是确定好的不更改的
- 组件可以『直接引入』(import)而无需通过继承(extend)
生命周期
挂载:当组件实例被创建并插入 DOM 时,其生命周期调用顺序如下:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
更新:当组件的props或state发生变化时会触发更新。其生命周期调用顺序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
卸载:当组件从 DOM 中移除时会调用如下方法:
- componentWillUnmount()
Hook
代码分割
- 引入用import()
1
2
3
4import("路径")
.then(math => {
console.log(math.add(10,9))
})
Refs 转发(ref: DOM内部)
- 转发 refs 到 DOM 组件: 允许某些组件接收ref,并将其传递给子组件
1
2
3
4
5
6
7
8const FancyButton = React.forwardRef((props, ref) => ( Ⅲ
<button ref={ref} className="FancyButton"> Ⅳ
{props.children}
</button>
))
// forwardRef 获取ref; createRef 创建一个React ref 并将其赋值给ref变量
const ref = React.createRef() Ⅰ
<FancyButton ref={ref}>Click me!</FancyButton> Ⅱ - 在高阶组件中转发 refs: 高阶组件ref只能传给外部的组件,内部组件可以通过从props获取forwardRef,在将它传给ref,在返回时就可以挂载到被包裹的子组件上
1
2
3
4
5const {forwardedRef} = this.props
return <Component ref= {forwardRef} />
return React.forwardRef((props,ref) => {
return <外部组件 {...props} forwardedRef={ref} />
})
高阶组件(HOC高阶组件是参数为组件,返回值为新组件的函数)
- 场景: 多个组件对同一个订阅和取消订阅的频繁调用
- 使用withSubscription(被包装组件,(订阅的组件,props)=>返回需要的数据)
- 将不相关的props传递给被包裹的组件
- refs不会被传递,render中不要使用HOC,静态方法也要复制(使用 hoist-non-react-statics 自动拷贝所有非 React 静态方法)
装饰器decorator
- 封装一个公用的方法
- 在写组件时,如果需要使用这个方法,则在这个组件上面添加一行『@方法』来使用这个公用的方法
- 这个方法可以通过参数来向这个类的原型上的构造函数添加新的属性
1
2
3
4
5
6const testDecorator = () => {
return (target: any) => {
target.isTeatable =true; //往DecoratorTest原型上的constructor添加了isTeatable =true
console.log(target);
}
}
render props
- 含义:不把一个组件强加在另一个组件内,而是在另一个组件被使用的『标签上使用』render props
- 作用:告知组件需要渲染什么内容的函数,变量符合时渲染对应内容
- 语法:
- render={(prop) => 要渲染的组件或内容}
- ortherName={() => 要渲染的组件或内容}
- 示例:
1
2
3
4<Child
login={userName => <h1>已登录</h1>} //userName 存在时就使用
nologin={() => <h1>请登录</h1>}
/>
Fragments
- 将子列表分组,可返回多个元素
- 形式: <React.Fragment /> 或 < />
- 作用: 像vue中的template一样,在html中不影响布局
PropTypes 进行类型检查
- 限制单个元素 PropTypes.element
- 默认值 defaultProps
- 任意类型数据 requiredAny: PropTypes.any.isRequired,
- 枚举类型 optionalEnum: PropTypes.oneOf([‘News’, ‘Photos’]),
Web Components (第三方UI组件)
- 使用的是class
多个className的模板字符串写法
1 | // 固定的样式名stone, 需要判断的样式名floatClass |
跳转页面
1 | import { Link,useNavigate } from 'react-router-dom' |