非受控组件

主要知识点

  • ref
  • defaultValue defaultChecked
  • 手动操作DOM元素

例子一:
  1. import React, { Component } from "react";
  2. class JsxBaseDemo extends Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. name: "lin",
  7. };
  8. this.nameInputRef = React.createRef(); // 创建 ref
  9. }
  10. alertName = () => {
  11. const elem = this.nameInputRef.current; // 通过 ref 获取 DOM 节点
  12. alert(elem.value); // 不是 this.state.name
  13. };
  14. render() {
  15. return (
  16. <>
  17. {/* 使用 defaultValue 而不是 value ,使用 ref */}
  18. <input defaultValue={this.state.name} ref={this.nameInputRef} />
  19. {/* state 并不会随着改变 */}
  20. <span>state.name: {this.state.name}</span>
  21. <br></br>
  22. <button onClick={this.alertName}>alert name</button>
  23. </>
  24. );
  25. }
  26. }
  27. export default JsxBaseDemo;

image.png

总结一下非受控组件的特点:

1.input中的值不受state的控制

2.默认绑定defaultValue而不是value

3.使用ref来获取DOM节点的值

4.state 并不会随着改变

例子二:
  1. import React, { Component } from "react";
  2. class JsxBaseDemo extends Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. name: "lin",
  7. };
  8. this.fileInputRef = React.createRef(); // 创建 ref
  9. }
  10. alertFile = () => {
  11. const elem = this.fileInputRef.current // 通过 ref 获取 DOM 节点
  12. alert(elem.files[0].name)
  13. }
  14. render() {
  15. return (
  16. <>
  17. <input type="file" ref={this.fileInputRef} />
  18. <button onClick={this.alertFile}>alert file</button>
  19. </>
  20. );
  21. }
  22. }
  23. export default JsxBaseDemo;

image.png

其实例子一可以用受控组件,也可以用非受控组件,但是例子二,就必须用DOM操作来获取文件名了。

那什么时候需要用DOM呢?不用用state来完成工作,这时候就需要用DOM操作了,也就引出了非受控组件,所以例子二是一个非常实际的场景。

非受控组件小结

  • 必须手动操作DOM元素,setState实现不了
  • 现实场景,文件上传,富文本编辑器

受控组件vs非受控组件选择

  • 优先使用受控组件,符合React的设计原则,数据驱动视图
  • 必须操作DOM时,再使用非受控组件

Portals

使用场景

  • 组件会按照既定层次嵌套渲染
  • 如何让组件渲染到父组件以外

例子:

假如我们有这样一个需求,想定义一个modal框,正常定义的结构是这样的。

  1. import React, { Component } from 'react'
  2. import JsxBaseDemo from './JsxBaseDemo'
  3. class index extends Component {
  4. render() {
  5. return (
  6. <div>
  7. <JsxBaseDemo></JsxBaseDemo>
  8. </div>
  9. )
  10. }
  11. }
  12. export default index
  13. class JsxBaseDemo extends Component {
  14. render() {
  15. return (
  16. <div className="modal"> {/* 给它个css定位fixed */}
  17. {this.props.children} {/* 相当于vue slot */}
  18. </div>
  19. );
  20. }
  21. }
  22. export default JsxBaseDemo;

我们来看一下正常的结构

image.png

使用Portals后的结构

  1. class JsxBaseDemo extends Component {
  2. render() {
  3. return ReactDOM.createPortal(
  4. <div className="modal">{this.props.children}</div>,
  5. document.body // DOM 节点
  6. );
  7. }
  8. }
  9. export default JsxBaseDemo;

image.png

发现modal已经挂载到body上了

Portals使用场景小结

  • overflow:hidden
  • 父组件z-index值太小
  • fixed需要放在body第一层级

Context

使用场景

  • 公共信息(语言、主题)如何传递给每个组件
  • 用props传递太繁琐
  • 用redux小题大做
  1. import React, { Component } from "react";
  2. // 创建 Context 填入默认值(任何一个 js 变量)
  3. const ThemeContext = React.createContext("light");
  4. // 底层组件 - 函数是组件
  5. function ThemeLink(props) {
  6. // const theme = this.context // 会报错。函数式组件没有实例,即没有 this
  7. // 函数式组件可以使用 Consumer
  8. return (
  9. <ThemeContext.Consumer>
  10. {(value) => <p>link's theme is {value}</p>}
  11. </ThemeContext.Consumer>
  12. );
  13. }
  14. // 底层组件 - class 组件
  15. class ThemedButton extends React.Component {
  16. // 指定 contextType 读取当前的 theme context。
  17. // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
  18. render() {
  19. const theme = this.context; // React 会往上找到最近的 theme Provider,然后使用它的值。
  20. return (
  21. <div>
  22. <p>button's theme is {theme}</p>
  23. </div>
  24. );
  25. }
  26. }
  27. ThemedButton.contextType = ThemeContext; // 指定 contextType 读取当前的 theme context。
  28. // 中间的组件再也不必指明往下传递 theme 了。
  29. function Toolbar(props) {
  30. return (
  31. <div>
  32. <ThemedButton />
  33. <ThemeLink />
  34. </div>
  35. );
  36. }
  37. class JsxBaseDemo extends Component {
  38. constructor(props) {
  39. super(props);
  40. this.state = {
  41. theme: "light",
  42. };
  43. }
  44. render() {
  45. return (
  46. <ThemeContext.Provider value={this.state.theme}>
  47. <Toolbar />
  48. <hr />
  49. <button onClick={this.changeTheme}>change theme</button>
  50. </ThemeContext.Provider>
  51. );
  52. }
  53. changeTheme = () => {
  54. this.setState({
  55. theme: this.state.theme === "light" ? "dark" : "light",
  56. });
  57. };
  58. }
  59. export default JsxBaseDemo;

创建: const ThemeContext = React.createContext(“light”);

顶层父组件使用提供一个值:

Class组件接收: static contextType = ThemeContext

Class组件使用:const theme = this.context; ||

  1. **ThemedButton.contextType = ThemeContext;**
  1. const ThemeContext = React.createContext(null)
  2. export default function ProviderDemo(){
  3. const [ contextValue , setContextValue ] = React.useState({ color:'#ccc', background:'pink' })
  4. return <div>
  5. <ThemeProvider value={ contextValue } >
  6. <Son />
  7. </ThemeProvider>
  8. </div>
  9. }
  10. // 类组件 - contextType 方式
  11. class ConsumerDemo extends React.Component{
  12. render(){
  13. const { color,background } = this.context
  14. return <div style={{ color,background } } >消费者</div>
  15. }
  16. }
  17. ConsumerDemo.contextType = ThemeContext
  18. const Son = ()=> <ConsumerDemo />

函数组件接收和使用:
{(value) =>
link’s theme is {value}
}

问与答

:context 与 props 和 react-redux 的对比?
: context解决了:

  • 解决了 props 需要每一层都手动添加 props 的缺陷。
  • 解决了改变 value ,组件全部重新渲染的缺陷。

异步组件

知识点

  • React.lazy
  • React.Suspense
  1. import React from "react";
  2. const ContextDemo = React.lazy(() => import("./JsxBaseDemo"));
  3. class App extends React.Component {
  4. render() {
  5. return (
  6. <div>
  7. <p>引入一个动态组件</p>
  8. <hr />
  9. <React.Suspense fallback={<div>Loading...</div>}>
  10. <ContextDemo />
  11. </React.Suspense>
  12. </div>
  13. );
  14. // 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
  15. // 2. 看 network 的 js 加载
  16. }
  17. }
  18. export default App;

创建异步组件:const ContextDemo = React.lazy(() => import(“./组件”))创建异步组件。

使用组件:<ContextDemo/>