# React基础知识

收录了v15、v16通用的基础知识点(轻量级)。

# 单向数据流

单向数据流:把整个react应用看作一个瀑布,那props就是瀑布水流的额外水源。

<Component data={this.state.data}>
1
  • 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} />
1
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 of value

# 受控组件 vs 非受控组件

在受控组件中,表单数据是由React组件来管理

在非受控组件中,表单数据是由DOM节点来处理。

# Context

Context可以用来共享“全局”(一个组件树)的数据。

# 创建Context对象

const MyContext = React.createContext(defaultValue); // 创建一个Context对象
// defaultValue会在 当组件所处的树中没有匹配到Provider时 使用。
1
2

# 定义Context.Provider

<MyContext.Provider value={/* 某个值 */}>
    /* ... */
</MyContext.Provider>
1
2
3

Provider接收一个value属性,传递给消费组件。

当 value 值变化时,它内部的所有消费组件都会重新渲染。

# 订阅Context变更

订阅 context 变更一共有 3种方法:Class.contextTypeContext.ConsumeruseContext

每一种订阅模式,都不会受到中间组件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;
1
2
3
4
5
6
7
8
9
10
11

# Context.Consumer

Context.Consumer也可以订阅到 context 变更。

<MyContext.Consumer>
    {value => /* */}
</MyContext.Consumer>
1
2
3

指定子组件为一个函数。回调里的value就是最近的 Provider提供的value。

# useContext

对于函数式组件,也可以通过useContext订阅到 context 变更:

function ThemedButton(props) {
    const value = useContext(MyContext); // 把Context对象传入useContext
}
1
2
3

# Demo

theme-context.js:(创建context对象)

export const MyContext = React.createContext('init')
1

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 />
    );
}
1
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;
1
2
3
4
5
6
7
8
9
10
11
12
13

对于函数式组件:

function ThemedButton(props) {
    // 订阅了context变化,能读取this.context
    const value = useContext(MyContext); // 把Context对象传入useContext
}
1
2
3
4

# 合成事件

React合成事件返回的是SyntheticEvent实例,能够兼容不同浏览器。

触发时机: 冒泡阶段(如:onClick、onChange)。

如果需要注册捕获阶段的事件处理函数,可以在事件名后紧接Capture(如:onClickCapture)

# 合成事件、原生事件的区别

方面 原生事件 合成事件
兼容性 不兼容跨浏览器 返回的是SyntheticEvent实例,能够兼容不同浏览器
绑定事件处理程序 传入字符串,函数的执行 传入函数
阻止默认行为 返回false 显示调用ev.preventDefault()

# SyntheticEvent实例

特点:

  • 兼容所有浏览器
  • 拥有原生事件接口(stopPropagationpreventDefault
  • 由于合并而来,可能会被重用 (即在事件回调触发完毕后,所有属性都会失效)

alt

# 触发Render的方式

  • 首次加载(ReactDOM.render()
  • setState()
  • props发生改变
  • forceUpdate()

其中,setStateprops发生改变都可以通过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>;
    }
}
1
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>;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

alt

# 为什么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对象
  }
}
1
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对象
}
1
2
3

在函数式组件内,通过useRef返回的ref对象可以在整个生命周期内保持不变。

# 结论

createRef:

  • 一般用于Class Component
  • 每次重新渲染都会使得引用地址发生变化(在生命周期内定义时除外)

useRef:

  • 一般用于函数式组件
  • 每次重新渲染不会导致引用地址发生改变

# ref的作用

  • ref用于HTML元素时,其.current属性为对应的DOM元素
  • ref用于Class Component时,其.current属性为组件的实例

以下打印了两者的.current属性: alt

对于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} />;
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这样,当父组件(即:引用<CustomInput ref={myRef} />的那个组件)就可以通过myRef.current来拿到CustomInput里的input元素了。

# ReactDOM.createPortal

React Portals

看官方文档上的意思,它可以在dom上挂载“表现在外层”,但它事件冒泡还是能够在原来声明处被父容器捕获到

alt

更新时间: 12/30/2020, 9:25:25 AM