# react官方文档
# React.Component
创建组件 链接
# React.StrictMode
在 “严格模式” 下开发时,React 会调用每个组件的函数两次,这可以帮助发现由不纯函数引起的错误。
在React中,双调用(即在严格模式下对组件函数进行双次调用)是React开发模式下的一个特性,用于帮助开发者发现潜在的问题。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
# React.PureComponent 纯组件
React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。
- 提高性能,如一个简单的值在state中,a=1,然后改成a=10,会渲染页面,当再点击还是setState({a:10}),React.Component还会触发渲染,需要手动添加shouldComponent返回false来阻止渲染,而纯组件可以自己有效的比较。
import React, { PureComponent } from 'react';
class Home extends PureComponent {
render(){
}
}
//PureComponent自动优化性能,内部已经添加了shouldComponentUpdate,所以不需要写这个生命周期
- 缺点是必须要class形式,且要注意是浅比较,如果state中有object的,则就无法有效的比较了
import React, { Component, PureComponent } from "react";
export default class PureComponentPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0,
// obj: {
// num: 2,
// },
};
}
setCounter = () => {
this.setState({
counter: 100,
// obj: {
// num: 200,
// },
});
};
render() {
const { counter, obj } = this.state;
console.log("render");
return (
<div>
<h1>PuerComponentPage</h1>
<div onClick={this.setCounter}>counter: {counter}</div>
</div>
);
}
}
# React.memo
会返回一个纯化(purified)的组件MemoFuncComponent,这个组件将会在JSX标记中呈现出来。当组件的参数props和状态状态发生改变时, React将会检查前一个状态和参数是否和下一个状态和参数是否相同,如果相同,组件将不会被渲染,如果不同,组件将会被重新渲染。
const Funcomponent = ()=> {
return (
<div>
Hiya!! I am a Funtional component
</div>
)
}
const MemodFuncComponent = React.memo(FunComponent)
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
React.PureComponent是给ES6的类组件使用的
React.memo(...)是给函数组件使用的
React.PureComponent减少ES6的类组件的无用渲染
React.memo(...)减少函数组件的无用渲染
# React.createElement()
React.createElement(
type,
[props],
[...children]
)
JSX 编写的代码将会被转换成使用 React.createElement() 的形式
import React from "react"
var child1 = React.createElement('li', null, 'one');
var child2 = React.createElement('li', null, 'two');
var content = React.createElement('ul', { className: 'teststyle' }, child1, child2);
function Model(props){
console.log(1)
return(content)
}
export default Model
# React.cloneElement()
以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。 新的子元素将取代现有的子元素,而来自原始元素的 key 和 ref 将被保留。
//子组件
import React,{Component} from "react"
class Model extends Component {
constructor(props) {
super(props)
this.state = {
flag: false
}
}
render() {
return (
<div style={{margin: "15px", border: "1px solid red"}}>
子元素:{this.props.subInfo}
<br/>
父组件属性count值: { this.props.parentState }
<br/>
<span onClick={ () => this.props.handleClick() }
style={{display:"inline-block",padding: "3px 5px", color:"#ffffff",
background: "green", borderRadius: "3px", cursor: "pointer"}}
>click me</span>
</div>
)
}
}
export default Model
import React,{Component} from 'react';
import Model from "./Model.js"
import './App.css';
class App extends Component {
constructor(props) {
super(props)
this.state = {
count: 1
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
count: this.state.count+1
})
console.log(this.state)
}
render() {
const childrenWithProps = React.Children.map(this.props.children, child => React.cloneElement(child,
{
parentState: this.state.count,
handleClick: this.handleClick
}
));
console.log(React.Children)
console.log(childrenWithProps)
return (
<div style={{border:"1px solid blue"}}>
<span>父容器:</span>
{ childrenWithProps }
</div>
)
}
}
export default App;
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import Model from './Model';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App>
<Model subInfo={"1"}/>
<Model subInfo={"2"}/>
</App>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
# React.createFactory()
如果使用JSX,通常不会直接调用 React.createFactory()。此辅助函数已废弃,建议使用 JSX 或直接调用 React.createElement() 来替代它。
# React.isValidElement(object)
验证对象是否为 React 元素,返回值为 true 或 false。
# React.Children
每个 props 都会包含一个特殊的属性:children,表示组件的内容,即所包裹的子组件。
React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。
# React.Children.map
React.Children.map(children, function[(thisArg)])
在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。 如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。 如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。
# React.Children.forEach
React.Children.forEach(children, function[(thisArg)])
与 React.Children.map() 类似,但它不会返回一个数组。
# React.Children.count
React.Children.count(children)
const childrenWithProps = React.Children.count(this.props.children);
返回 children 中的组件总数量,等同于通过 map 或 forEach 调用回调函数的次数。
# React.Children.only
React.Children.only(children)
验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。
React.Children.only() 不接受 React.Children.map() 的返回值,因为它是一个数组而并不是 React 元素。
# React.Children.toArray
React.Children.toArray(children)
将 children 这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。 当你想要在渲染函数中操作子节点的集合时,它会非常实用,特别是当你想要在向下传递 this.props.children 之前对内容重新排序或获取子集时。
React.Children.toArray() 在拉平展开子节点列表时,更改 key 值以保留嵌套数组的语义。也就是说,toArray 会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。
# React.Fragment
不需额外创建顶层元素时使用
# Portals
可以渲染子节点到不同的 DOM 子树中
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
let k1=document.getElementById("k1")
function App(props) {
return (
<div>
{
ReactDOM.createPortal(
<div>19999</div>,
k1)
}
</div>
);
}
export default App;
# setState()
- 修改state错误的案例
state = {
count : 0,
list: [1,2,3],
person: {
name:'jack',
age:18
}
}
// 直接修改简单类型Number
this.state.count++
++this.state.count
this.state.count += 1
this.state.count = 1
// 直接修改数组
this.state.list.push(123)
this.state.list.spice(1,1)
// 直接修改对象
this.state.person.name = 'rose'
setState(partialState, callback)
- partialState : object|function 用于产生与当前state合并的子集。
- callback : function state更新之后被调用。
setState()将对组件state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式。
将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React会延迟调用它,然后通过一次传递更新多个组件。
setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。
为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数中,这两种都可以保证在应用更新后触发。
除非 shouldComponentUpdate() 返回 false,否则 setState() 将始终执行重新渲染操作。 如果可变对象被使用,且无法在 shouldComponentUpdate() 中实现条件渲染,那么仅在新旧状态不一时调用 setState()可以避免不必要的重新渲染
参数一为带有形式参数的 updater 函数:
(state, props) => stateChange
setState() 的第一个参数除了接受函数外,还可以接受对象类型:
setState(stateChange[, callback])
//会将传入的对象浅层合并到新的 state 中,例如,调整购物车商品数:
this.setState({quantity: 2})
这种形式的 setState() 也是异步的,并且在同一周期内会对多个 setState 进行批处理。 例如,如果在同一周期内多次设置商品数量增加,则相当于:
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次。 如果后续状态取决于当前状态,建议使用 updater 函数的形式代替:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
- setState只有在合成事件和生命周期函数中是异步的,在原生事件和setTimeout中都是同步的 ,这里的异步其实是批量更新。如果用的是合成事件和生命周期中,要保证获取的是变动后的,则可以放在setState的回调函数中执行
- 为了性能,setState连续调用,会被合并覆盖,如果要避免,使用setState第一个参数使用函数return对象
- 事件绑定注意bind(this)或者使用fun = ()=>{}形式
import React, { Component } from "react";
export default class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state ={
num:0
}
}
componentDidMount() {
const btn =document.querySelector("#btn")
btn.onclick =()=>{
this.setState({
num: this.state.num+1
})
console.log('count1',this.state.num)
}
}
fun(){
// console.log(this)
// this.setState({
// num: this.state.num+1
// },()=>{
// console.log('count2',this.state.num)
// })
// console.log('count3',this.state.num)
// setTimeout(()=>{
// this.setState({
// num: this.state.num+1
// },()=>{
// console.log('count4',this.state.num)
// })
// console.log('count5',this.state.num)
// },0)
this.setState((state)=>{
return {num:state.num+1}
},()=>{
console.log('count6',this.state.num)
})
console.log('count7',this.state.num)
}
fun1 = ()=>{
this.fun()
this.fun()
this.fun()
this.fun()
}
render() {
return (<div>
<button onClick={this.fun.bind(this)}>btn</button>
<button id='btn'>btn2</button>
<button onClick={this.fun1}>btn3</button>
<div>{this.state.num}</div>
</div>);
}
}
- this.ff变更,虽然不是写在state中,但是fun方法调用了setState了,所以ff也会更新。如果没有使用setState,调用了forceUpdate也可以实现更新
import React, { Component } from "react";
export default class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state ={
num:0
}
this.ff=1000
}
componentDidMount() {
}
fun(){
this.setState((state)=>{
return {num:state.num+1}
},()=>{
console.log('count6',this.state.num)
})
console.log('count7',this.state.num)
this.ff++
}
render() {
return (<div>
<button onClick={this.fun.bind(this)}>btn</button>
<div>{this.ff}</div>
</div>);
}
}
# forceUpdate()
component.forceUpdate(callback)
默认情况下,当组件的state或props发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据, 则可以调用 forceUpdate() 强制让组件重新渲染。
调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。 但其子组件会触发正常的生命周期,包括shouldComponentUpdate()。如果标记发生变化,React 仍将只更新 DOM。应该避免使用 forceUpdate()。
# react事件处理
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。 在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault 。 如表单提交的自动刷新
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
内置组件(
function Button({ onSmash, children }) {
return (
<button onClick={onSmash}>
{children}
</button>
);
}
export default function App() {
return (
<div>
<Button onSmash={() => alert('正在播放!')}>
播放电影
</Button>
<Button onSmash={() => alert('正在上传!')}>
上传图片
</Button>
</div>
);
}
当你的组件支持多种交互时,你可以根据不同的应用程序命名事件处理函数 prop。例如,一个 Toolbar 组件接收 onPlayMovie 和 onUploadImage 两个事件处理函数:
export default function App() {
return (
<Toolbar
onPlayMovie={() => alert('正在播放!')}
onUploadImage={() => alert('正在上传!')}
/>
);
}
function Toolbar({ onPlayMovie, onUploadImage }) {
return (
<div>
<Button onClick={onPlayMovie}>
播放电影
</Button>
<Button onClick={onUploadImage}>
上传图片
</Button>
</div>
);
}
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
极少数情况下,你可能需要捕获子元素上的所有事件,即便它们阻止了传播。例如,你可能想对每次点击进行埋点记录,传播逻辑暂且不论。那么你可以通过在事件名称末尾添加 Capture 来实现这一点:
<div onClickCapture={() => { /* 这会首先执行 */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
每个事件分三个阶段传播:
- 它向下传播,调用所有的 onClickCapture 处理函数。
- 它执行被点击元素的 onClick 处理函数。
- 它向上传播,调用所有的 onClick 处理函数。
捕获事件对于路由或数据分析之类的代码很有用,但你可能不会在应用程序代码中使用它们。
# react事件机制
React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等
在React中这套事件机制被称之为合成事件
合成事件是 React模拟原生 DOM事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器
根据 W3C规范来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口,例如:
const button = <button onClick={handleClick}>按钮</button>
如果想要获得原生DOM事件,可以通过e.nativeEvent属性获取
const handleClick = (e) => console.log(e.nativeEvent);;
const button = <button onClick={handleClick}>按钮</button>
虽然onclick看似绑定到DOM元素上,但实际并不会把事件代理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件去监听
这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象
当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率也有很大提升
关于React合成事件与原生事件执行顺序,可以看看下面一个例子
import React from 'react';
class App extends React.Component{
constructor(props) {
super(props);
this.parentRef = React.createRef();
this.childRef = React.createRef();
}
componentDidMount() {
console.log("React componentDidMount!");
this.parentRef.current?.addEventListener("click", () => {
console.log("原生事件:父元素 DOM 事件监听!");
});
this.childRef.current?.addEventListener("click", () => {
console.log("原生事件:子元素 DOM 事件监听!");
});
document.addEventListener("click", (e) => {
console.log("原生事件:document DOM 事件监听!");
});
}
parentClickFun = () => {
console.log("React 事件:父元素事件监听!");
};
childClickFun = () => {
console.log("React 事件:子元素事件监听!");
};
render() {
return (
<div ref={this.parentRef} onClick={this.parentClickFun}>
<div ref={this.childRef} onClick={this.childClickFun}>
分析事件执行顺序
</div>
</div>
);
}
}
export default App;
原生事件:子元素 DOM 事件监听!
原生事件:父元素 DOM 事件监听!
React 事件:子元素事件监听!
React 事件:父元素事件监听!
原生事件:document DOM 事件监听!
React 所有事件都挂载在 document 对象上
当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件
所以会先执行原生事件,然后处理 React 事件
最后真正执行 document 上挂载的事件
所以想要阻止不同时间段的冒泡行为,对应使用不同的方法,对应如下:
阻止合成事件间的冒泡,用e.stopPropagation()
阻止合成事件与最外层 document 上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation()
阻止合成事件与除最外层document上的原生事件上的冒泡,通过判断e.target来避免
document.body.addEventListener('click', e => {
if (e.target && e.target.matches('div.code')) {
return;
}
this.setState({ active: false, }); });
}
# 向事件处理程序传递参数
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
合成事件是被池化的,在执行完事件处理程序后,其参数(即事件对象)的属性将会被丢弃,例如,通过定时器异步调用事件对象的 type 属性,如下所示。
class Btn extends React.Component {
handle(e) {
console.log(e.type); //"click"
setTimeout(function() {
console.log(e.type); //null
}, 0);
}
render() {
return <button onClick={this.handle}>提交</button>;
}
}
单击“提交”按钮,在控制台首先会输出“click”,然后再输出 null。如果想要保持事件对象的属性,那么可以调用它的 persist()方法。继续上一个例子,只要在 handle()方法中加一 句 e.persist() ,代码如下所示,就能在定时器中输出“click”。
handle(e) {
console.log(e.type); //"click"
setTimeout(function() {
console.log(e.type); //"click"
}, 0);
e.persist();
}
# 条件渲染
- if条件
- &&
- 三目运算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
------------------------
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
# React.createContext
使用 context, 可以避免通过中间元素传递 props; Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。谨慎使用,因为这会使得组件的复用性变差。
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
- 先创建一个context.js文件(与父子孙同个目录),默认值为一个对象。
import React from "react";
const MyContext = React.createContext({text:'luck'});
export default MyContext
- 然后,对父组件进行改写,引入context,使用一个 Provider 来将当前的 value 传递给以下的组件树,value为传递的值。
import React from 'react';
import Children from './Children';
import MyContext from './context';
class Parent extends React.Component {
constructor(props) {
super(props);
}
// 使用一个 Provider 来将当前的 value 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
render(){
return (
<div style={{backgroundColor:'#f7ba2a',padding:'20px',width:'500px',margin:'auto',textAlign:'center'}}>
<p>context通信实例</p>
<MyContext.Provider value={{text:'good luck'}}>
<Children></Children>
</MyContext.Provider>
</div>
)
}
}
export default Parent
- 子组件为中间层,不做处理,用于包裹“孙”组件。
import React from 'react';
import Grandson from './Grandson';
class Children extends React.Component {
render(){
return (
<div>
<Grandson></Grandson>
</div>
)
}
}
export default Children
新增一个“孙”组件,同样需引入context,在组件内部添加static contextType = MyContext,此时将能通过this.context直接获取到上层距离最近的Provider传递的值,此时this.context = {text:good luck},即父组件传递value。
import React from 'react';
import MyContext from './context';
class Grandson extends React.Component {
static contextType = MyContext
render(){
return (
<div style={{backgroundColor:'#13ce66',padding:'10px',width:'200px',margin:'auto',marginTop:'20px'}}>
<p>通过context传过来:</p>
<span style={{color:'blue'}}>{this.context.text}</span>
</div>
)
}
}
export default Grandson
如果想孙-->父向上传值,可以通过回调的方式,对父组件进行传值修改,在传过来的对象中添加一个属性,
里面绑定父组件的方法value={{text:'good luck',toParent:this.fromGranson}}
import React from 'react';
import Children from './Children';
import MyContext from './context';
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg:''
};
this.fromGranson = this.fromGranson.bind(this)
}
fromGranson(val){
this.setState({
msg:val
})
}
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
render(){
return (
<div style={{backgroundColor:'#f7ba2a',padding:'20px',width:'500px',margin:'auto',textAlign:'center'}}>
<p>context通信实例</p>
<span style={{color:'red'}}>{this.state.msg}</span>
<MyContext.Provider value={{text:'good luck',toParent:this.fromGranson}}>
<Children></Children>
</MyContext.Provider>
</div>
)
}
}
export default Parent
- 然后在孙组件中添加一个按钮,绑定方法,执行函数回调
import React from 'react';
import MyContext from './context';
import { Button } from 'element-react'
class Grandson extends React.Component {
static contextType = MyContext
constructor(props) {
super(props);
this.toParent = this.toParent.bind(this)
}
toParent(){
this.context.toParent('孙组件向父组件传数据')
}
render(){
return (
<div style={{backgroundColor:'#13ce66',padding:'10px',width:'200px',margin:'auto',marginTop:'20px'}}>
<p>通过context传过来:</p>
<span style={{color:'blue'}}>{this.context.text}</span>
<div><Button onClick={this.toParent}>context向上</Button></div>
</div>
)
}
}
export default Grandson
# 如何使用Context
context api给出三个概念:React.createContext()、Provider、Consumer;
React.createContext()
这个方法用来创建context对象,并包含Provider、Consumer两个组件 <Provider />、<Consumer />
const {Provider, Consumer} = React.createContext();
- Provider 数据的生产者,通过value属性接收存储的公共状态,来传递给子组件或后代组件
eg:
<Provider value={/* some value */}>
- Consumer 数据的消费者,通过订阅Provider传入的context的值,来实时更新当前组件的状态
eg:
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
值得一提的是每当Provider的值发生改变时, 作为Provider后代的所有Consumers都会重新渲染