非受控组件
主要知识点
- ref
- defaultValue defaultChecked
- 手动操作DOM元素
例子一:
import React, { Component } from "react";
class JsxBaseDemo extends Component {
constructor(props) {
super(props);
this.state = {
name: "lin",
};
this.nameInputRef = React.createRef(); // 创建 ref
}
alertName = () => {
const elem = this.nameInputRef.current; // 通过 ref 获取 DOM 节点
alert(elem.value); // 不是 this.state.name
};
render() {
return (
<>
{/* 使用 defaultValue 而不是 value ,使用 ref */}
<input defaultValue={this.state.name} ref={this.nameInputRef} />
{/* state 并不会随着改变 */}
<span>state.name: {this.state.name}</span>
<br></br>
<button onClick={this.alertName}>alert name</button>
</>
);
}
}
export default JsxBaseDemo;
总结一下非受控组件的特点:
1.input中的值不受state的控制
2.默认绑定defaultValue而不是value
3.使用ref来获取DOM节点的值
4.state 并不会随着改变
例子二:
import React, { Component } from "react";
class JsxBaseDemo extends Component {
constructor(props) {
super(props);
this.state = {
name: "lin",
};
this.fileInputRef = React.createRef(); // 创建 ref
}
alertFile = () => {
const elem = this.fileInputRef.current // 通过 ref 获取 DOM 节点
alert(elem.files[0].name)
}
render() {
return (
<>
<input type="file" ref={this.fileInputRef} />
<button onClick={this.alertFile}>alert file</button>
</>
);
}
}
export default JsxBaseDemo;
其实例子一可以用受控组件,也可以用非受控组件,但是例子二,就必须用DOM操作来获取文件名了。
那什么时候需要用DOM呢?不用用state来完成工作,这时候就需要用DOM操作了,也就引出了非受控组件,所以例子二是一个非常实际的场景。
非受控组件小结
- 必须手动操作DOM元素,setState实现不了
- 现实场景,文件上传,富文本编辑器
受控组件vs非受控组件选择
- 优先使用受控组件,符合React的设计原则,数据驱动视图
- 必须操作DOM时,再使用非受控组件
Portals
使用场景
- 组件会按照既定层次嵌套渲染
- 如何让组件渲染到父组件以外
例子:
假如我们有这样一个需求,想定义一个modal框,正常定义的结构是这样的。
import React, { Component } from 'react'
import JsxBaseDemo from './JsxBaseDemo'
class index extends Component {
render() {
return (
<div>
<JsxBaseDemo></JsxBaseDemo>
</div>
)
}
}
export default index
class JsxBaseDemo extends Component {
render() {
return (
<div className="modal"> {/* 给它个css定位fixed */}
{this.props.children} {/* 相当于vue slot */}
</div>
);
}
}
export default JsxBaseDemo;
我们来看一下正常的结构
使用Portals后的结构
class JsxBaseDemo extends Component {
render() {
return ReactDOM.createPortal(
<div className="modal">{this.props.children}</div>,
document.body // DOM 节点
);
}
}
export default JsxBaseDemo;
发现modal已经挂载到body上了
Portals使用场景小结
- overflow:hidden
- 父组件z-index值太小
- fixed需要放在body第一层级
Context
使用场景
- 公共信息(语言、主题)如何传递给每个组件
- 用props传递太繁琐
- 用redux小题大做
import React, { Component } from "react";
// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext("light");
// 底层组件 - 函数是组件
function ThemeLink(props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this
// 函数式组件可以使用 Consumer
return (
<ThemeContext.Consumer>
{(value) => <p>link's theme is {value}</p>}
</ThemeContext.Consumer>
);
}
// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
render() {
const theme = this.context; // React 会往上找到最近的 theme Provider,然后使用它的值。
return (
<div>
<p>button's theme is {theme}</p>
</div>
);
}
}
ThemedButton.contextType = ThemeContext; // 指定 contextType 读取当前的 theme context。
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
<ThemeLink />
</div>
);
}
class JsxBaseDemo extends Component {
constructor(props) {
super(props);
this.state = {
theme: "light",
};
}
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
<Toolbar />
<hr />
<button onClick={this.changeTheme}>change theme</button>
</ThemeContext.Provider>
);
}
changeTheme = () => {
this.setState({
theme: this.state.theme === "light" ? "dark" : "light",
});
};
}
export default JsxBaseDemo;
创建: const ThemeContext = React.createContext(“light”);
顶层父组件使用提供一个值:
Class组件接收: static contextType = ThemeContext
Class组件使用:const theme = this.context; ||
**ThemedButton.contextType = ThemeContext;**
const ThemeContext = React.createContext(null)
export default function ProviderDemo(){
const [ contextValue , setContextValue ] = React.useState({ color:'#ccc', background:'pink' })
return <div>
<ThemeProvider value={ contextValue } >
<Son />
</ThemeProvider>
</div>
}
// 类组件 - contextType 方式
class ConsumerDemo extends React.Component{
render(){
const { color,background } = this.context
return <div style={{ color,background } } >消费者</div>
}
}
ConsumerDemo.contextType = ThemeContext
const Son = ()=> <ConsumerDemo />
函数组件接收和使用:
{(value) =>
link’s theme is {value}
}
问与答
问:context 与 props 和 react-redux 的对比?
答: context解决了:
- 解决了 props 需要每一层都手动添加 props 的缺陷。
- 解决了改变 value ,组件全部重新渲染的缺陷。
异步组件
知识点
- React.lazy
- React.Suspense
import React from "react";
const ContextDemo = React.lazy(() => import("./JsxBaseDemo"));
class App extends React.Component {
render() {
return (
<div>
<p>引入一个动态组件</p>
<hr />
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo />
</React.Suspense>
</div>
);
// 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
// 2. 看 network 的 js 加载
}
}
export default App;
创建异步组件:const ContextDemo = React.lazy(() => import(“./组件”))创建异步组件。
使用组件:<ContextDemo/>