# React基础知识
收录了v15、v16通用的基础知识点(轻量级)。
# 单向数据流
单向数据流:把整个react应用看作一个瀑布,那props就是瀑布水流的额外水源。
<Component data={this.state.data}>
Component
组件不知道data
来源。- 任何state总是所属于某个特定的组件,而且从该state派生的任何数据或UI(Any data or UI derived from that state)只能影响比它们下方的组件。
# 受控组件与非受控组件
受控组件: 由 state
管理输入值,由 事件处理函数
去修改state。
会阻止用户输入
<input type="text">
、<textarea>
这类标签都接受一个value
属性,可以利用它实现受控组件。
# 非受控组件
我们可以使用ref
去获取DOM节点的value值。
you can use a
ref
to get from values from the DOM.
this.input = React.createRef()
console.log(this.input.current.value) // this.input.current拿到DOM节点
<input ref={this.input} />
2
3
4
5
由上,可以通过ref来读取当前input的value。
不需要像 受控组件 一样去定义state挂在value上、编写onChange事件去取value从而改变state
对于非受控组件,我们可以定义一个defaultValue
而不是value
,来达到给组件赋予初始值,而不控制后续更新。
With an uncontrolled component, you often want React to specify the initial value, but leave subsequent updates uncontrolled. You can specify a
defaultValue
attribute instead ofvalue
# 受控组件 vs 非受控组件
在受控组件中,表单数据是由React组件
来管理
在非受控组件中,表单数据是由DOM节点
来处理。
# Context
Context可以用来共享“全局”(一个组件树)的数据。
# 创建Context对象
const MyContext = React.createContext(defaultValue); // 创建一个Context对象
// defaultValue会在 当组件所处的树中没有匹配到Provider时 使用。
2
# 定义Context.Provider
<MyContext.Provider value={/* 某个值 */}>
/* ... */
</MyContext.Provider>
2
3
Provider
接收一个value
属性,传递给消费组件。
当 value 值变化时,它内部的所有消费组件都会重新渲染。
# 订阅Context变更
订阅 context 变更一共有 3种方法:Class.contextType
、Context.Consumer
、useContext
。
每一种订阅模式,都不会受到中间组件
shouldComponentUpdate
的影响,即子组件依旧能触发更新。
# Class.contextType
将Context对象
挂载到class上的contextType
属性,可以订阅到 context 变更。这样就可以通过this.context
取得Context
的值。
this.context
可以在任何生命周期(包括render函数)中使用
以下 Way1、Way2 是等价的:
class MyClass extends React.Component {
// Way1:
static contextType = MyContext;
render() {
let value = this.context;
}
}
// Way2:
// MyClass.contextType = MyContext;
2
3
4
5
6
7
8
9
10
11
# Context.Consumer
Context.Consumer
也可以订阅到 context 变更。
<MyContext.Consumer>
{value => /* */}
</MyContext.Consumer>
2
3
指定子组件为一个函数。回调里的value
就是最近的 Provider提供的value。
# useContext
对于函数式组件,也可以通过useContext
订阅到 context 变更:
function ThemedButton(props) {
const value = useContext(MyContext); // 把Context对象传入useContext
}
2
3
# Demo
theme-context.js:(创建context对象)
export const MyContext = React.createContext('init')
app.js(生产者):
// 引入context对象
import { MyContext } from './theme-context';
class App extends React.Component {
render() {
return (
// 定义Provider,提供value值
<MyContext.Provider value={'这里是根组件的水源'}>
<Toolbar />
</MyContext.Provider>
);
}
}
function Toolbar() {
return (
<ThemedButton />
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
themed-button.js(消费者):
// 引入context对象
import { MyContext } from './theme-context';
class ThemedButton extends React.Component {
render() {
let theme = this.context; // Using this.context to consume the nearest current value of that Context type
return /**/;
}
}
// 订阅了context变化,能读取this.context
ThemedButton.contextType = MyContext;
2
3
4
5
6
7
8
9
10
11
12
13
对于函数式组件:
function ThemedButton(props) {
// 订阅了context变化,能读取this.context
const value = useContext(MyContext); // 把Context对象传入useContext
}
2
3
4
# 合成事件
React合成事件返回的是SyntheticEvent实例
,能够兼容不同浏览器。
触发时机: 冒泡阶段(如:onClick、onChange)。
如果需要注册捕获阶段的事件处理函数,可以在事件名后紧接
Capture
(如:onClickCapture)
# 合成事件、原生事件的区别
方面 | 原生事件 | 合成事件 |
---|---|---|
兼容性 | 不兼容跨浏览器 | 返回的是SyntheticEvent实例 ,能够兼容不同浏览器 |
绑定事件处理程序 | 传入字符串,函数的执行 | 传入函数 |
阻止默认行为 | 返回false | 显示调用ev.preventDefault() |
# SyntheticEvent实例
特点:
- 兼容所有浏览器
- 拥有原生事件接口(
stopPropagation
、preventDefault
) - 由于合并而来,可能会被重用 (即在事件回调触发完毕后,所有属性都会失效)
# 触发Render的方式
- 首次加载(
ReactDOM.render()
) - setState()
- props发生改变
- forceUpdate()
其中,setState
、props
发生改变都可以通过shouldComponentUpdate
决定是否执行render
注意: 1、只是render的执行与否,数据还是会修改掉的
2、只要在相同的DOM节点中渲染
<Clock />
,就仅有一个Clock组件的class实例被创建使用。
# 函数内部的this绑定
# 利用bind
class HomeIndex extends Component {
constructor(props) {
super(props);
this.state = { name: 'heshiyu' };
this.addCount = this.addCount.bind(this); // 或者在render里再调用bind
}
addCount() {
console.log(this.state.name);
}
render() {
return <button onClick={this.addCount}>Click</button>;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 利用箭头函数
class HomeIndex extends Component {
constructor(props) {
super(props);
}
addCount = () => {
console.log(this.state.name);
}
render() {
return <button onClick={this.addCount}>Click</button>;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 为什么React要用className?
因为class
在JavaScript里是关键字,而JSX是JavaScript的扩展。
# ref对象
ref对象:是可变的对象(每次都是修改其下的.current
属性),并且在整个生命周期内保持不变。
# 通过createRef和useRef来创建ref对象
这两种方式都可以创建ref对象,但有区别。
# createRef
一般我们会在constructor
里定义ref对象:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef(); // <-- 通过createRef创建ref对象
}
}
2
3
4
5
6
7
也正是React的生命周期,让createRef
只执行了一次。
但实际上,每次调用它都会重新生成一个ref对象(引用地址会发生改变),What's the difference between useRef and createRef?
由于引用地址发生变化,对于函数式组件就需要使用useRef
# useRef
function App() {
const inputRef = useRef(null); // <-- 通过useRef创建ref对象
}
2
3
在函数式组件内,通过useRef
返回的ref对象可以在整个生命周期内保持不变。
# 结论
createRef:
- 一般用于
Class Component
- 每次重新渲染都会使得引用地址发生变化(在生命周期内定义时除外)
useRef:
- 一般用于
函数式组件
- 每次重新渲染不会导致引用地址发生改变
# ref的作用
- 当
ref
用于HTML元素时,其.current
属性为对应的DOM元素
- 当
ref
用于Class Component
时,其.current
属性为组件的实例
以下打印了两者的.current
属性:
对于
Class Component
需要注意:方法要bind到组件实例上,否则无法通过.current
读取到方法(若是箭头函数写法则可忽略)
# 将ref作用于函数式组件(forwardRef搭配useImperativeHandle)
若ref用于函数式组件
,因为它没有实例,react会提示你用forwardRef
那就需要将forwardRef
搭配useImperativeHandle
使用,来转发这个ref
但最终,还是需要让ref来指向一个DOM元素或者Class Component
。
# 转发ref
假设有一个函数式组件CustomInput
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () =>
// 这个返回的对象表示:向父组件暴露的属性
({
focus: () => {
inputRef.current.focus();
}
})
);
// 最终还是需要让ref来指向一个DOM元素(或一个Class Component)
return <input ref={inputRef} />;
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这样,当父组件(即:引用<CustomInput ref={myRef} />
的那个组件)就可以通过myRef.current
来拿到CustomInput
里的input元素了。
# ReactDOM.createPortal
看官方文档上的意思,它可以在dom上挂载“表现在外层”,但它事件冒泡还是能够在原来声明处被父容器捕获到