UseContext——跨组件层级穿值
context即上下文
js中有个执行上下文一样,高所组件当下的环境是什么.
createContext创建的ObjectContext在Parent和Son中都需要使用到
const ObjectContext = createContext()
在provider(父组件)中的使用方法是
<ObjectContext.Provider value={initValue}>
<Son />
<ObjectContext.provider />
在consumer(子组件)中使用的方法是
function Son() {
const count = useContext(ObjectContext)
<div>count<div />
}
完整代码
import "./styles.css";
import React, { useState, createContext, useContext } from "react";
const CountContext = createContext();
function Son() {
const count = useContext(CountContext); // 一句话就可以得到count
console.log("useContext(createContext())的返回值", count);
return <h2 style={{ border: "1px solid red" }}>子组件{count}</h2>;
}
function Parent() {
const [count, setCount] = useState(0);
console.log("createContext()的返回值", CountContext);
console.log("useContext(createContext())的返回值", count);
return (
<div style={{ border: "1px solid black" }}>
<p>You clicked {count} times</p>
<button
onClick={() => {
console.log("父组件函数触发,状态修改");
setCount(count + 1);
}}
>
click me
</button>
<CountContext.Provider value={count}>
<div>
<Son />
</div>
</CountContext.Provider>
</div>
);
}
export default Parent;
// index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
rootElement
);
UseReducer:集中状态管理
useReducer接受一个处理函数和初始值为参数,返回一个实时值和一个修改值的dispatch方法
function ReducerDemo() {
const [count, dispatch] = useReducer((state, action) => {
switch (action) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}, 0);
return (
<div>
<h2>现在的分数是{count}</h2>
<button onClick={() => dispatch('add')}>Increment</button>
<button onClick={() => dispatch('sub')}>Decrement</button>
</div>
);
}
export default ReducerDemo;
用useReducer和useContext实现redux功能:
根组件的结构如下,一个context.provider包裹两个consumer
// example6/Example6.js
import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';
import { Color } from './color'; // 引入Color组件
function Example6() {
return (
<div>
<Color>
<ShowArea />
<Buttons />
</Color>
</div>
);
}
export default Example6;
provider中内容如下:通过useContext的provider传递useReducer返回的实时值和修改方法
import React, { createContext, useReducer } from 'react';
export const ColorContext = createContext({});
export const UPDATE_COLOR = 'UPDATE_COLOR';
const reducer = (state, action) => {
switch (action.type) {
case UPDATE_COLOR:
return action.color;
default:
return state;
}
};
export const Color = props => {
const [color, dispatch] = useReducer(reducer, 'blue');
return <ColorContext.Provider value={{ color, dispatch }}>{props.children}</ColorContext.Provider>;
};
接受状态的组件showArea:
// example6/ShowArea.js
import React, { useContext } from 'react';
import { ColorContext } from './color';
function ShowArea() {
const { color } = useContext(ColorContext);
return <div style={{ color }}>字体颜色为{color}</div>;
}
export default ShowArea;
接受修改状态的Button:
// example6/Button.js
import React, { useContext } from 'react';
import { ColorContext, UPDATE_COLOR } from './color';
function Buttons() {
const { dispatch } = useContext(ColorContext);
return (
<div>
<button
onClick={() => {
dispatch({ type: UPDATE_COLOR, color: 'red' });
}}
>
红色
</button>
<button
onClick={() => {
dispatch({ type: UPDATE_COLOR, color: 'yellow' });
}}
>
黄色
</button>
</div>
);
}
export default Buttons;
渲染:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Example from './example6/Example6';
ReactDOM.render(<Example />, document.getElementById('root'));
useMemo ——避免当前组件更新时不必要的大量计算
当一个组件的属性或者状态改变时,会触发重新渲染.
函数内的变量也会再创建一次.如果一个变量是大量计算得来的,那么这个计算过程也会执行一次.
这就是性能的损耗
import React, { useState, useMemo } from 'react';
function Example7() {
const [A, setA] = useState('A属性');
const [B, setB] = useState('B属性');
const [C, setC] = useState('C属性');
return (
<div style={{ border: '1px solid black' }}>
<div>父组件</div>
<button
style={{ border: '1px solid black', borderRadius: 5 }}
onClick={() => {
setA(`A属性,${new Date().getTime()}`);
}}>
改变属性A
</button>
<button
style={{ border: '1px solid black', borderRadius: 5 }}
onClick={() => {
setB(`B属性,${new Date().getTime()}`);
}}>
改变属性B
</button>
<ChildComponent a={A} b={B} c={C} />
</div>
);
}
function ChildComponent({ a, b, c }: { a: any; b: any; c: any }) {
function process(name: any) {
console.log(`${name} is processing`);
return `----${name}---`;
}
const aResult = useMemo(() => process(a), [a]);
const bResult = useMemo(() => process(b), [b]);
// c没有被缓存,导致A、B属性变化时,process(c)也会执行
const cResult = process(c);
return (
<div style={{ border: '1px solid red' }}>
<div>{aResult}</div>
<div>{bResult}</div>
<div>{cResult}</div>
</div>
);
}
export default Example7;
将需要繁重计算的方法用useMemo包裹,并添加上它的依赖项
可以避免组件更新时函数没有必要的执行
const aResult = useMemo(() => process(a), [a]);
原来是
const cResult = process(c);
useMemo和useEffect的执行先后顺序问题
useCallback ——避免父组件更新时,子组件没有必要的渲染
useCallback和memo连用,达到类组件中pureComponent的效果,但是使用时要注意如何使其真正达到性能优化。
在这个案例中,父组件内维护了一个状态,父组件传递一个函数C给子组件。
通过这个函数C,可以修改父组件的状态,状态的修改带来组件的更新,包括C的更新。
- 在没有性能优化/性能优化无效的情况下,触发函数C,处理父组件的重新渲染,还包括子组件的重新渲染,
- 在有效的性能优化的情况下,触发函数C,只会导致父组件的重新渲染
- 没有做任何性能优化
import React, { memo, useState, useCallback } from 'react';
const ParentComponent = () => {
console.log('--------parent rendering--------');
const [count, setCount] = useState(0);
const C = () => {
console.log('clicked ChildrenComponent');
setCount(preCount => preCount + 1);
};
const F = () => {
console.log('clicked ParentComponent');
setCount(preCount => preCount + 1);
};
return (
<div>
<button onClick={F}>ParentComponent </button>
<div>count ={count}</div>
<ChildrenComponent C={C} />
</div>
);
};
const ChildrenComponent = ({ C }) => {
console.log('--------ChildrenComponent rending--------------');
return <button onClick={C}>ChildrenComponent 触发C</button>;
};
export default ParentComponent;
无论点击子组件还是父组件,都会导致两次渲染。
- usecallback+memo性能优化
import React, { memo, useState, useCallback } from 'react';
const ParentComponent = () => {
console.log('--------parent rendering--------');
const [count, setCount] = useState(0);
const C = () => {
console.log('clicked ChildrenComponent');
setCount(preCount => preCount + 1);
};
// 性能优化1:将传递给子组件的函数用useCallback包裹
const callbackC = useCallback(() => {
C();
}, []);
const F = () => {
console.log('clicked ParentComponent');
setCount(preCount => preCount + 1);
};
return (
<div>
<button onClick={F}>ParentComponent </button>
<div>count ={count}</div>
<ChildrenComponent C={callbackC} />
</div>
);
};
// 性能优化2:将自组件用memo包裹
const ChildrenComponent = memo(({ C }) => {
console.log('--------ChildrenComponent rending--------------');
return <button onClick={C}>ChildrenComponent 触发C</button>;
});
export default ParentComponent;
无论点击子元素还是父组件,都只会导致父组件的渲染。
- pureComponent
import React, { Component, Fragment, PureComponent } from 'react';
// 性能优化:子组件继承PureComponent
class ChildClass extends PureComponent {
render() {
console.log('-----childComponent Rendering------');
const { C } = this.props;
return (
<div>
<button onClick={C}>ChildrComponent 触发C</button>
</div>
);
}
}
export default class ParentComponent extends Component {
state = {
count: 1,
};
F = () => {
console.log('click parentComponent');
this.setState({ count: this.state.count + 1 });
};
C = () => {
console.log('click childComponent');
this.setState({ cnt: this.state.count + 1 });
};
render() {
console.log('-----parentComponent Rendering------');
return (
<Fragment>
<div>
<button onClick={this.F}>ParentComponent</button>
<ChildClass C={this.C} />
</div>
</Fragment>
);
}
}
无论点击子元素还是父组件,都只会导致父组件的渲染。
useRef——保存节点或者当前的值
useRef可以用来对应一个dom节点或者是一个实时值 对应dom节点时使用方法如下
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.value = 'Hello ,useRef';
console.log('inputRef', inputEl);
};
<input ref={inputEl} type="text" />
对应实时值的使用方法如下
const [text, setText] = useState('jspang');
const textRef = useRef();
useEffect(() => {
textRef.current = text;
console.log('textRef:', textRef);
});
完整代码
import React, { useRef, useState, useEffect } from 'react';
function Example8() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.value = 'Hello ,useRef';
console.log('inputRef', inputEl);
};
// -----------关键代码--------start
const [text, setText] = useState('jspang');
const textRef = useRef();
useEffect(() => {
textRef.current = text;
console.log('textRef:', textRef);
});
// ----------关键代码--------------end
return (
<>
{/* 保存input的ref到inputEl */}
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>在input上展示文字</button>
<br />
<br />
<input
value={text}
onChange={e => {
setText(e.target.value);
}}
/>
</>
);
}
export default Example8;
useImperativeHandle——父组件调用子组件方法
// 子组件定义
function FancyButton(props: ButtonProps, ref) {
const testFunction = () => {
console.log(123);
};
useImperativeHandle(ref, () => ({
test: () => {
testFunction();
},
}));
return (
<button ref={ref} className="FancyButton">
{props.children}
</button>
);
}
// 一定要配置forwardRef使用,来转发ref
const FancyButtonRef = React.forwardRef<unknown, ButtonProps>(FancyButton);
// 父组件调用
const ref = React.useRef();
<FancyButtonRef ref={ref}>Click me!</FancyButtonRef>;
ref.current.test();
useState
useState 有状态时使用上一次保存的状态,没有状态时使用默认数值,有状态的组件是用户在填写的表单,是一个能记住上一次浏览位置的长列表,它需要一个位置来表征它的状态,