React を探索し学習する過程で、フロントエンド開発の魅力と課題が徐々にわかってきました。React は Facebook が立ち上げたオープンソースの JavaScript ライブラリであり、そのユニークな考え方と強力な機能でフロントエンド開発のトレンドをリードしています。この記事では、フロントエンド分野を研究している開発者を支援するために、私の React 学習体験を共有し、メモを要約します。
1.Hello React ケース
<div id="root"></div>;
// 类组件和函数式组件
class App extends React.Component {
// 组件数据
constructor() {
super();
this.state = {
message: "Hello World",
};
// 对需要绑定的方法, 提前绑定好this 或者方法写成箭头函数上下文指向React组件
this.btnClick = this.btnClick.bind(this);
}
// 组件方法(实例方法)
btnClick() {
// 1.将state中message值修改掉 2.自动重新执行render函数函数
this.setState({
message: "Hello React",
});
}
// 渲染内容 render方法
render() {
return (
<div>
<h2>{
this.state.message}</h2>
<button onClick={
this.btnClick}>修改文本</button>
</div>
);
}
}
// 将组件渲染到界面上
const root = ReactDOM.createRoot(document.querySelector("#root"));
// App根组件
root.render(<App />);
コンテキストで定義されていない場合は
this
、this
デフォルトでグローバル オブジェクトが使用されます。これは通常、ブラウザのグローバル オブジェクトですwindow
。Reactクラスコンポーネントにおいてバインディングメソッドが表示されていない場合、this
メソッド呼び出し時にthis
コンポーネントインスタンスのコンテキストが失われundefined
、実行時エラーが発生します。この問題を回避するには、
.bind()
または アロー関数を使用してメソッドを明示的にバインドthis
するか、アロー関数を使用してコンストラクター内でメソッドを定義し、メソッドがthis
コンポーネント インスタンスを指すようにすることができます。
2. リストのレンダリング
....
// 封装App组件
class App extends React.Component {
constructor() {
super()
this.state = {
data: ["Vue", "React", "Angular"]
}
}
render() {
// 1.对data进行for循环
// const liEls = []
// for (let i = 0; i < this.state.data.length; i++) {
// const item = this.state.movies[i]
// const liEl = <li>{item}</li>
// liEls.push(liEl)
// }
// 2.data数组 => liEls数组
// const liEls = this.state.data.map(movie => <li>{movie}</li>)
return (
<div>
<h2>电影列表</h2>
<ul>
{
this.state.movies.map(movie => <li>{
movie}</li>)}
</ul>
</div>
)
}
}
......
3.JSXの構文規則
3.1 コンテンツの挿入
- jsxのコメント
- JSX 埋め込み変数を子要素として使用する
- 状況 1:変数の型が数値、文字列、または配列の場合、直接表示できます。
- ケース 2:変数が null、未定義、またはブール型の場合、内容は空です。
- null、未定義、およびブール値を表示したい場合は、それらを文字列に変換する必要があります。
- toString メソッド、空文字列との連結、String (変数) など、変換方法は多数あります。
- ケース 3: Object オブジェクト型は子要素として使用できません (React の子としては無効です)
- JSX 埋め込み式
- 算術式
- 三項演算子
- 関数を実行します**
// jsx语法规则
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
count: 1000,
names: ["紫陌", "张三", "王五"],
aaa: undefined,
bbb: null,
ccc: true,
friend: {
name: "zimo" },
firstName: "zimo",
lastName: "紫陌",
age: 20,
list: ["aaa", "bbb", "ccc"],
};
}
render() {
// 解构拿到值
const {
message, names, count, aaa, bbb, ccc, friend, firstName, lastName, age } = this.state;
// 对内容进行运算后显示(插入表示)
const ageText = age >= 18 ? "成年人" : "未成年人";
const liEls = this.state.list.map((item) => <li>{
item}</li>);
return (
<div>
{
/* 1.Number / String / Array直接显示出来*/}
<h2>{
message}</h2>
<h2>{
count}</h2>
<h2>{
names}</h2>
{
/* 2.undefined/null/Boolean 默认不展示,三种方法都可以显示出来*/}
<h2>{
String(aaa)}</h2>
<h2>{
bbb + ""}</h2>
<h2>{
ccc.toString()}</h2>
{
/* 3.Object类型不能作为子元素展示 */}
<h2>{
friend.name}</h2> {
/* zimo */}
<h2>{
Object.keys(friend)[0]}</h2> {
/* name */}
{
/* 4.可以插入对应表达式 */}
<h2>{
10 + 20}</h2>
<h2>{
firstName + "" + lastName}</h2>
{
/* 5.插入三元表达式 */}
<h2>{
ageText}</h2>
<h2>{
age >= 18 ? "成年人" : "未成年人"}</h2>
{
/* 6.可以调用方法获取结果 */}
<ul>{
liEls}</ul>
<ul>
{
this.state.list.map((item) => (
<li>{
item}</li>
))}
</ul>
<ul>{
this.getList()}</ul>
</div>
);
}
getList() {
const liEls = this.state.list.map((item) => <li>{
item}</li>);
return liEls;
}
}
3.2 バインディング属性
// 1.定义App根组件
class App extends React.Component {
constructor() {
super();
this.state = {
title: "紫陌",
imgURL: "https:*****",
href: "https://*****",
isActive: true,
objStyle: {
color: "red", fontSize: "30px" },
};
}
render() {
const {
title, imgURL, href, isActive, objStyle } = this.state;
// 需求: isActive: true -> active
// 1.class绑定的写法一: 字符串的拼接
const className = `abc cba ${
isActive ? "active" : ""}`;
// 2.class绑定的写法二: 将所有的class放到数组中
const classList = ["abc", "cba"];
if (isActive) classList.push("active");
// 3.class绑定的写法三: 第三方库classnames -> npm install classnames
return (
<div>
{
/* 1.基本属性绑定 */}
<h2 title={
title}>我是h2元素</h2>
<img src={
imgURL} alt="" />
<a href={
href}>百度一下</a>
{
/* 2.绑定class属性: 最好使用className */}
<h2 className={
className}>哈哈哈哈</h2>
<h2 className={
classList.join(" ")}>哈哈哈哈</h2>
{
/* 3.绑定style属性: 绑定对象类型 */}
<h2 style={
{
color: "red", fontSize: "30px" }}>紫陌YYDS</h2>
<h2 style={
objStyle}>紫陌</h2>
</div>
);
}
}
jsx 構文規則:
1.仮想 DOM を定義する場合は、引用符を記述しないでください。
2. JS 式をタグに混在させる場合は、{} を使用します。
3.スタイルのクラス名を指定する場合は、class ではなく、className を使用します。
5.ルートタグは 1 つだけ
6.ラベルは閉じていなければなりません
7.ラベルのイニシャル
( 1). 小文字で始まる場合、タグは html 内の同名の要素に変換されます。 html 内にタグに対応する同名の要素が存在しない場合は、エラーが報告されます。
( 2)。大文字で始まる場合、react は対応するコンポーネントをレンダリングします。コンポーネントが定義されていない場合は、エラーが報告されます。
3.3 イベントバインディング
- React イベントの命名には、純粋な小文字ではなくキャメルケースが使用されます。
- {} を介してイベント処理関数を渡す必要があります。この関数は、イベントの発生時に実行されます。
class App extends React.Component {
constructor() {
super();
this.state = {
count: 10,
};
this.btn1Click = this.btn1Click.bind(this);
}
btn1Click() {
console.log("btn1", this);
this.setState({
count: this.state.count + 1 });
}
btn2Click = () => {
console.log("btn2", this);
this.setState({
count: this.state.count + 1 });
};
btn2Click = () => {
console.log("btn2", this);
this.setState({
count: this.state.count + 1 });
};
btn3Click() {
console.log("btn2", this);
this.setState({
count: this.state.count + 1 });
}
render() {
const {
count } = this.state;
return (
<div>
<h2>当前计数:{
count}</h2>
{
/* 1.this绑定方式一:bind绑定 */}
<button onClick={
this.btn1Click}>按钮1</button>
{
/* 2.this.绑定方式二:ES6 class fields */}
<button onClick={
this.btn2Click}>按钮2</button>
{
/* 3.this绑定方式三:直接传入一个箭头函数(重要) */}
<button onClick={
() => console.log("btn3Click")}>按钮3</button>
<button onClick={
() => this.btn3Click()}>按钮4</button>
</div>
);
}
}
3.4 イベントパラメータの転送
- イベント関数を実行するとき、イベントオブジェクト、その他のパラメータなどのパラメータ情報を取得する必要がある場合があります。
- シナリオ 1: イベント オブジェクトを取得する
- 多くの場合、イベント オブジェクトに何かを実行させる (デフォルトの動作を防ぐなど) 必要があります。
- デフォルトでは、イベント オブジェクトが直接渡され、関数はイベント オブジェクトを取得できます。
- シナリオ 2: さらにパラメータを取得する
- さらに多くのパラメーターがある場合、最善の方法は、アクティブに実行されるイベント関数であるアロー関数を渡し、その他の関連パラメーターを渡すことです。
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
};
}
btnClick(event, name, age) {
console.log("btnClick", event, this);
console.log("name,age", name, age);
}
render() {
return (
<div>
{
/* 1.event参数传递 */}
<button onClick={
this.btnClick.bind(this)}>按钮1</button>
<button
onClick={
(event) => {
this.btnClick(event);
}}
>
按钮2
</button>
{
/* 额外参数传递 */}
<button onClick={
this.btnClick.bind(this, "zimo", 18)}>按钮3(不推荐)</button>
<button onClick={
(event) => this.btnClick(event, "zimo", 18)}>按钮4</button>
</div>
);
}
}
3.5 条件付きレンダリング
// 1.定义App根组件
class App extends React.Component {
constructor() {
super();
this.state = {
isShow: false,
flag: undefined,
};
}
render() {
const {
isShow, flag } = this.state;
// 1.条件判断方式一: 使用if进行条件判断
let showElement = null;
if (isShow) {
showElement = <h2>显示紫陌</h2>;
} else {
showElement = <h1>隐藏紫陌</h1>;
}
return (
<div>
{
/* 1.方式一: 根据条件给变量赋值不同的内容 */}
<div>{
showElement}</div>
{
/* 2.方式二: 三元运算符 */}
<div>{
isShow ? <h2>显示</h2> : <h2>隐藏</h2>}</div>
{
/* 3.方式三: &&逻辑与运算 */}
{
/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
<div> {
flag && <h2>{
flag}</h2>} </div>
</div>
);
}
}
3.6 条件付きリストのレンダリング
- React では、リストを表示する最も一般的な方法は、配列のマップ高階関数を使用することです。
- 多くの場合、配列内のデータを表示する前に、一部のコンテンツをフィルターで除外するなど、データに対して何らかの処理を行う必要があります: filter 関数
- たとえば、配列の内容の一部をインターセプトするには、スライス関数を使用します。
class App extends React.Component {
constructor() {
super();
this.state = {
students: [
{
id: 111, name: "zimo", score: 199 },
{
id: 112, name: "aniy", score: 98 },
{
id: 113, name: "james", score: 199 },
{
id: 114, name: "curry", score: 188 },
],
};
}
render() {
const {
students } = this.state;
/* 第一种分别写 */
//分数大于100学生进行展示
const filterStudent = students.filter((item) => {
return item.score > 100;
});
//分数大于100前两名信息
const sliceStudents = filterStudent.slice(0, 2);
return (
<div>
<h2>列表数据</h2>
<div>
{
/* 第二种方式链式调用 */}
{
students
.filter((item) => item.score > 100)
.slice(0, 2)
.map((item) => {
return (
<div key={
item.id}>
<h2>学号: {
item.id}</h2>
<h3>姓名: {
item.name}</h3>
<h4>分数:{
item.score}</h4>
</div>
);
})}
</div>
</div>
);
}
}
3.7 JSXの本質
-
JSX は、React.createElement(component, props, …children) 関数の糖衣構文にすぎません。すべての jsx は最終的に React.createElement の関数呼び出しに変換されます。
-
createElement は 3 つのパラメータを渡す必要があります。
-
パラメータ 1: タイプ
- 現在の ReactElement のタイプ。
- ラベル要素の場合は、「div」を表す文字列を使用します。
- コンポーネント要素の場合は、コンポーネントの名前を直接使用します。
-
パラメータ 2: config
./images -
jsx のすべてのプロパティは、オブジェクトのプロパティと値として config に保存されます
-
たとえば、要素のクラスとして className を渡します。
-
パラメータ 3: 子供
- タグに格納されたコンテンツは、children 配列に格納されます。
- ソースコード分析
- バベル変換
- JSX は構文変換の実行に babel を使用しており、作成するすべての jsx コードは babel に依存する必要があります。
- 変換プロセスは、babel の公式 Web サイトですぐに確認できます: https://babeljs.io/repl/#?presets=react
babel によって変換されたコードは直接実行できます。インターフェイスは引き続き通常どおりレンダリングできます。
3. コンポーネント開発
3.1 render関数の戻り値
- renderが呼び出される と、 this.propsとthis.stateの変更がチェックされ 、次のいずれかのタイプが返されます。
- 反応 要素:
- 通常は JSX 経由で作成されます。
- 例えば、
これは、React によって DOM ノードとしてレンダリングされ、
<MyComponent />
React によってカスタム コンポーネントとしてレンダリングされます。 - そうであるかどうか
または
<MyComponent />
両方とも React 要素です。
- 配列または フラグメント: render メソッドが複数の要素を返すことができるようにします。
- ポータル: 子ノードを異なる DOM サブツリーにレンダリングできます。
- 文字列または数値タイプ: DOM 内のテキスト ノードとしてレンダリングされます。
- boolean または null : 何もレンダリングしません。
3.2 機能コンポーネント
- 関数コンポーネントはfunctionを使って定義された関数ですが、この関数はクラスコンポーネントのrender関数と同じ内容を返します。
- 関数コンポーネントには独自の特性があります (フックは異なります)。
- ライフサイクルはなく、更新およびマウントされますが、ライフサイクル機能はありません。
- this キーワードはコンポーネント インスタンスを指すことはできません (コンポーネント インスタンスがないため)。
- 内部状態 (状態) はありません。
// 函数式组件
function App(props) {
// 返回值: 和类组件中render函数返回的是一致
return <h1>Hello zimo</h1>;
}
export default App;
4.反応ライフサイクル
4.1 一般的なライフサイクル関数図
- すべてのライフサイクル機能
4.2 ライフサイクル関数の例
親コンポーネント: APP コンポーネント
import React, {
Component } from "react";
import Hello from "./Hello";
class App extends Component {
constructor() {
super();
this.state = {
isShow: true,
};
}
isShowFun() {
this.setState({
isShow: !this.state.isShow,
});
}
render() {
const {
isShow } = this.state;
return (
<div>
我是父组件
{
isShow && <Hello />}
<button onClick={
() => this.isShowFun()}>显示隐藏子组件</button>
</div>
);
}
}
export default App;
サブコンポーネント: こんにちは
import React, {
Component } from "react";
class Hello extends Component {
constructor() {
// 1.构造方法: constructor
console.log("1. Hello constructor");
super();
this.state = {
message: "你好紫陌",
};
}
updateText() {
this.setState({
message: "你好啊,zimo~",
});
}
// 2.执行render函数
render() {
console.log("2. Hello render");
const {
message } = this.state;
return (
<div>
<h2>{
message}</h2>
<p>{
message}这是子组件</p>
<button onClick={
() => this.updateText()}>修改文本</button>
</div>
);
}
// 3.组件被渲染到DOM:被挂载到DOM
componentDidMount() {
console.log("Hello组件挂载完成");
}
// 4. 组件的DOM被更新完成:DOM发生更新
componentDidUpdate() {
console.log("Hello组件更新完成");
}
// 5.组件从DOM中卸载掉:从DOM移除掉
componentWillUnmount() {
console.log("Hello组件卸载完成");
}
// 不常用的生命周期函数
shouldComponentUpdate() {
return true; //false 不会更新数据
}
// 等等
}
export default Hello;
実行結果
4.3 あまり使用されないライフサイクル関数
- pgetSnapshotBeforeUpdate: React が DOM を更新する前にコールバックされる関数。DOM が更新される前にいくつかの情報 (スクロール位置など) を取得できます。
- pgetDerivedStateFromProps: 状態の値が常に props に依存する場合に使用され、このメソッドは状態を更新するオブジェクトを返します。
- 等
4.4 概要: 新旧のライフサイクル関数
旧バージョンのライフサイクル機能:
- 初期化フェーズ: ReactDOM.render() によってトリガー - 初期レンダリング 1.constructor() 2.componentWillMount() 3.render() 4.componentDidMount() =====> 一般的に使用されるのは、
このフックで初期化を行うことです。例: タイマーの開始、ネットワーク要求の送信、メッセージの購読- 更新フェーズ: コンポーネントまたは親コンポーネント内の this.setSate() によってトリガーされます。 render 1. shouldComponentUpdate() 2.componentWillUpdate() 3. render() =====> 使用する必要があるもの 4.componentDidUpdate()
- コンポーネントのアンインストール: ReactDOM.unmountComponentAtNode() によってトリガーされます 1.componentWillUnmount() =====> 一般的に使用され、
タイマーの終了やメッセージの購読解除など、いくつかの仕上げ処理はこのフックで行われます。
新しいバージョンのライフサイクル関数:
- 初期化フェーズ: ReactDOM.render() によってトリガー - 初期レンダリング 1.constructor() 2.getDerivedStateFromProps 3.render() 4.componentDidMount() =====> 通常、
このフックで何らかの初期化を行うために使用されます。 as : タイマーを開始し、ネットワーク要求を送信し、メッセージを購読します- 更新フェーズ: コンポーネント内の this.setSate() または親コンポーネントの再レンダリングによってトリガーされます 1. getDerivedStateFromProps 2. shouldComponentUpdate() 3. render() 4. getSnapshotBeforeUpdate 5.componentDidUpdate()
- コンポーネントのアンインストール: ReactDOM.unmountComponentAtNode() によってトリガーされます 1.componentWillUnmount() =====> 一般的に使用され、
タイマーの終了やメッセージの購読解除など、いくつかの仕上げ処理はこのフックで行われます。
5. コンポーネント通信
5.1 父から息子への受け継ぎ(小道具)
- 親コンポーネントは、属性 ** = 値 **;の形式でデータを子コンポーネントに渡します。
- 子コンポーネントは、親コンポーネントからpropsパラメータを通じて渡されたデータを取得します。
親コンポーネント: メイン:
export class Main extends Component {
constructor() {
super();
this.state = {
banners: [],
productList: [],
};
}
componentDidMount() {
axios.get("http://123.207.32.32:8000/home/multidata").then((res) => {
const banners = res.data.data.banner.list;
const recommend = res.data.data.recommend.list;
this.setState({
banners,
productList: recommend,
});
});
}
render() {
const {
banners, productList } = this.state;
return (
<div className="main">
<div>Main</div>
<MainBanner banners={
banners} title="紫陌" />
<MainBanner />
<MainProductList productList={
productList} />
</div>
);
}
}
サブコンポーネント: メインバナー
import React, {
Component } from "react";
import PropTypes from "prop-types";
export class MainBanner extends Component {
/* es2022的语法等同于下面 MainBanner.defaultProps */
// static defaultProps = {
// banners:[],
// title:"默认标题"
// }
render() {
// console.log(this.props)
const {
title, banners } = this.props;
return (
<div className="banner">
<h2>封装一个轮播图: {
title}</h2>
<ul>
{
banners?.map((item) => {
return <li key={
item.acm}>{
item.title}</li>;
})}
</ul>
</div>
);
}
}
//MainBanner传入props类型进行验证
MainBanner.propTypes = {
banners: PropTypes.array,
title: PropTypes.string,
};
//MainBanner传入的props的默认值
MainBanner.defaultProps = {
banners: [],
title: "默认标题",
};
export default MainBanner;
props タイプの検証:
- React v15.5 以降、React.PropTypes は別のパッケージ、prop-types ライブラリに移動されました。
- 詳しい検証方法については、公式 Web サイトを参照してください: https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
- たとえば、配列とその配列に含まれる要素を確認します。
- たとえば、オブジェクトを確認し、オブジェクトに含まれるキーと値の型を確認します。
- たとえば、ネイティブが必要な場合は、requiredFunc: PropTypes.func.isRequired を使用します。
- 何も渡されず、デフォルト値を使用したい場合はどうすればよいでしょうか?
- defaultProps を使用するだけです
ユウ・シェンの場合:
1. props限制案例:
----------------------------------------------------------------------------------------------------------
//创建组件
class Person extends React.Component{
render(){
// console.log(this);
const {
name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{
name}</li>
<li>性别:{
sex}</li>
<li>年龄:{
age+1}</li>
</ul>
)
}
}
//对标签属性进行类型、必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
.............
function speak(){
console.log('我说话了');
}
2.props的简写方式也就是利用es2022语法
---------------------------------------------------------------------------------------------------
//创建组件
class Person extends React.Component{
constructor(props){
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super(props)
console.log('constructor',this.props);
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
render(){
// console.log(this);
const {
name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{
name}</li>
<li>性别:{
sex}</li>
<li>年龄:{
age+1}</li>
</ul>
)
}
}
-----------------------------------------------------------------------------------------------------
3. 函数式组件传递props
//创建组件
function Person (props){
const {
name,age,sex} = props
return (
<ul>
<li>姓名:{
name}</li>
<li>性别:{
sex}</li>
<li>年龄:{
age}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
5.2 子传父
- Vue では、これはカスタム イベントを通じて行われます。
- React では、メッセージは props を介して渡されます。これには、親コンポーネントが子コンポーネントにコールバック関数を渡し、子コンポーネントでこの関数を呼び出すだけで済みます。
場合:
親コンポーネント: APP
export class App extends Component {
constructor() {
super();
this.state = {
counter: 100,
};
}
//调用修改变量
changeCounter(count) {
this.setState({
counter: this.state.counter + count,
});
}
render() {
const {
counter } = this.state;
return (
<div>
<h2>当前计数为:{
counter}</h2>
<AddCounter addClick={
(count) => this.changeCounter(count);}/>
</div>
);
}
}
サブコンポーネント: AddCounter
export class AddCounter extends Component {
addCount(count) {
//拿到父组件定义的函数传变量
this.props.addClick(count);
}
render() {
return (
<div>
<button
onClick={(e) => {
this.addCount(10);
}}
>
点我加10
</button>
</div>
);
}
}
図:
6. React のスロット
- Vue には、スロットを使用してスロット効果を実現する固定メソッドがあります。
- React はスロットに関して非常に柔軟で、これを行うには 2 つの方法があります。
- コンポーネントの子要素。
- props 属性は React 要素を渡します。
-
1 つ目の方法:各コンポーネントはprops.childrenを取得できます 。これには、コンポーネントの開始タグと終了タグの間のコンテンツが含まれます。
子によって実装されるソリューションは実現可能ですが、欠点があります。インデックス値を介して受信要素を取得する際にエラーが発生しやすく、受信ネイティブ要素を正確に取得できません。
親コンポーネント: APP
export class App extends Component {
render() {
return (
<div>
{
/* 1.使用children实现插槽 */}
<NavBar>
<button>按钮</button>
<h2>zimo</h2>
<span>紫陌</span>
</NavBar>
</div>
);
}
}
サブコンポーネント: ナビゲーションバー
export class NavBar extends Component {
render() {
const {
children } = this.props;
return (
<div className="nav-bar">
<div className="left">{
children[0]}</div>
<div className="center">{
children[1]}</div>
<div className="right">{
children[2]}</div>
</div>
);
}
}
2. 2 番目の方法:もう 1 つの解決策は、 propsを使用することです 。特定の属性名を使用すると、受け渡しと取得をより正確に行うことができます。
親コンポーネント: APP
export class App extends Component {
render() {
const spanEl = <span>紫陌</span>;
return (
<div>
{
/* 2.使用props实现插槽 */}
<NavBar leftSlot={
<button>按钮2</button>} centerSlot={
<h2>zimo</h2>} rightSlot={
spanEl} />
</div>
);
}
}
サブコンポーネント: ナビゲーションバー
export class NavBar extends Component {
render() {
const {
leftSlot, centerSlot, rightSlot } = this.props;
return (
<div className="nav-bar">
<div className="left">{
leftSlot}</div>
<div className="center">{
centerSlot}</div>
<div className="right">{
rightSlot}</div>
</div>
);
}
}
React スコープのスロット効果の実装
タブケースのデモ:
7. 親子以外のコミュニケーション(コンテキスト)
- Context は、コンポーネント ツリーの各レベルで明示的に props を渡すことなく、コンポーネント間でそのような値を共有する方法を提供します。
7.1コンテキスト関連API
-
React.createContext
-
共有する必要がある Context オブジェクトを作成します。
-
コンポーネントが Context をサブスクライブする場合、コンポーネントは、それ自体に最も近い一致するプロバイダーから現在のコンテキスト値を読み取ります。
-
defaultValue は、コンポーネントがトップレベルの検索プロセス中に対応するプロバイダーを見つけられなかった場合、デフォルト値が使用されることを意味します。
-
// 1.创建一个Context const userContext = React.createContext(defaultValue);
-
-
コンテキストプロバイダー
-
各 Context オブジェクトは Provider React コンポーネントを返します。これにより、使用するコンポーネントはコンテキストの変更をサブスクライブできます。
-
プロバイダーは値属性を受け取り、それを使用側コンポーネントに渡します。
-
プロバイダーは、複数のコンシューマー コンポーネントと対応する関係を持つことができます。
-
複数のプロバイダーをネストして使用することもでき、内側のレイヤーが外側のレイヤーのデータを上書きします。
-
プロバイダーの値が変更されると、プロバイダー内のすべての使用コンポーネントが再レンダリングされます。
-
<MyContext.Provider value={ /* 某个值*/}
-
-
クラス.contextType
-
クラスにマウントされた contextType 属性は、React.createContext() によって作成された Context オブジェクトに再割り当てされます。
-
これにより、this.context を使用して最新の Context の値を使用できるようになります。
-
レンダリング関数を含め、どのライフサイクルでもアクセスできます。
-
//设置组件的contextType为某一个Context //class.contextType = ThemeContext HomeInfo.contextType = ThemeContext;
- コンテキスト.コンシューマ
-
ここで、React コンポーネントはコンテキストの変更をサブスクライブすることもできます。これにより、機能コンポーネントでコンテキストをサブスクライブできるようになります。
-
ここでは、子としての機能のアプローチが必要です。
-
この関数は現在のコンテキスト値を受け取り、React ノードを返します。
-
<UserContext.Consumer> { (value) => { return <h2>Info User: { value.nickname}</h2>; }} </UserContext.Consumer>
-
Context.Consumerをいつ使用するか?
値を使用するコンポーネントが機能コンポーネントの場合。
コンポーネント内で複数のコンテキストを使用する必要がある場合。
ケースその 1:
APP 親コンポーネント:
theme-context.jsx
--------------------------------------------------------------------------------------------------------
import React from "react"
// 1.创建一个Context
const ThemeContext = React.createContext({
color: "blue", size: 10 })
export default ThemeContext
user-context.jsx
---------------------------------------------------------------------------------------------------------
import React from "react"
// 1.创建一个Context
const UserContext = React.createContext()
export default UserContext
APP.jsx
----------------------------------------------------------------------------------------------------------
import React, {
Component } from 'react'
import Home from './Home'
import ThemeContext from "./context/theme-context"
import UserContext from './context/user-context'
import Profile from './Profile'
export class App extends Component {
render() {
return (
<div>
<h2>App</h2>
{
/* 通过ThemeContext中Provider中value属性为后代提供数据 */}
<UserContext.Provider value={
{
nickname: "kobe", age: 30}}>
<ThemeContext.Provider value={
{
color: "red", size: "30"}}>
<A/>
</ThemeContext.Provider>
</UserContext.Provider>
<Profile/>
</div>
)
}
}
コンポーネント:
export class Home extends Component {
render() {
return (
<div>
<B />
<C />
</div>
);
}
}
Bコンポーネント:クラスコンポーネント
import ThemeContext from "./context/theme-context";
import UserContext from "./context/user-context";
export class B extends Component {
render() {
// 4.第四步操作: 获取数据, 并且使用数据
console.log(this.context);
return (
<div>
{
/*获取最近的context */}
<h2>HomeInfo: {
this.context.color}</h2>
{
/*获取指定的context */}
<UserContext.Consumer>
{
(value) => {
return <h2>Info User: {
value.nickname}</h2>;
}}
</UserContext.Consumer>
</div>
);
}
}
// 3.第三步操作: 设置组件的contextType为某一个Context
HomeInfo.contextType = ThemeContext;
Cコンポーネント:関数コンポーネント
import ThemeContext from "./context/theme-context";
function C() {
return (
<div>
{
/* 函数式组件中使用Context共享的数据 */}
<ThemeContext.Consumer>
{
(value) => {
return <h2> Banner theme:{
value.color}</h2>;
}}
</ThemeContext.Consumer>
</div>
);
}
export default HomeBanner;
ケース 2:
import React, {
Component } from "react";
//创建Context对象
const MyContext = React.createContext();
const {
Provider, Consumer } = MyContext;
export default class A extends Component {
state = {
username: "tom", age: 18 };
render() {
const {
username, age } = this.state;
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{
username}</h4>
<Provider value={
{
username, age }}>
<B />
</Provider>
</div>
);
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C />
</div>
);
}
}
class C extends Component {
//声明接收context
static contextType = MyContext;
render() {
const {
username, age } = this.context;
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>
我从A组件接收到的用户名:{
username},年龄是{
age}
</h4>
</div>
);
}
}
function C() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>
我从A组件接收到的用户名:
<Consumer>{
(value) => `${
value.username},年龄是${
value.age}`}</Consumer>
</h4>
</div>
);
}
在应用开发中一般不用context, 一般都它的封装react插件;
8. setState を使用する
- 開発中に、state の値を直接変更してインターフェイスを更新することはできません。
- 状態を変更した後、React が最新の状態に基づいてインターフェースを再レンダリングすることを期待しているためですが、この方法で変更された場合、React はデータが変更されたことを認識しません。
- React は、データの変更を監視するための、Vue2 の Object.defineProperty や Vue3 の Proxy に似たメソッドを実装しません。
- データが変更されたことを React に通知するには、setState を使用する必要があります。
8.1 setState の 3 つの使用法
ケースその 1:
export class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "Hello Zimo",
};
}
changeText() {
// 1.基本使用
this.setState({
message: "你好啊, 紫陌",
});
// 2.setState可以传入一个回调函数
// 好处一: 可以在回调函数中编写新的state的逻辑
// 好处二: 当前的回调函数会将之前的state和props传递进来
this.setState((state, props) => {
// 1.编写一些对新的state处理逻辑
// 2.可以获取之前的state和props值
console.log(this.state.message, this.props);
return {
message: "你好啊, 紫陌",
};
});
// 3.setState在React的事件处理中是一个异步调用
// 如果希望在数据更新之后(数据合并), 获取到对应的结果执行一些逻辑代码
// 那么可以在setState中传入第二个参数: callback
this.setState({
message: "你好啊, 紫陌" }, () => {
console.log("++++++:", this.state.message); //后打印,setState异步
});
console.log("------:", this.state.message); //先打印
}
render() {
const {
message } = this.state;
return (
<div>
<h2>message: {
message}</h2>
<button onClick={
(e) => this.changeText()}>修改文本</button>
</div>
);
}
}
ケース 2: ユウ兄
import React, {
Component } from "react";
export default class Demo extends Component {
state = {
count: 0 };
add = () => {
//对象式的setState
//1.获取原来的count值
const {
count } = this.state;
//2.更新状态
this.setState({
count: count + 1 }, () => {
console.log(this.state.count);
});
//console.log('12行的输出',this.state.count); //0
//函数式的setState
this.setState((state) => ({
count: state.count + 1 }));
};
render() {
return (
<div>
<h1>当前求和为:{
this.state.count}</h1>
<button onClick={
this.add}>点我+1</button>
</div>
);
}
}
8.2 setState が非同期になるように設計されているのはなぜですか?
- setState は非同期になるように設計されており、パフォーマンスを大幅に向上させることができます。
- setState が呼び出されるたびに更新される場合、render 関数が頻繁に呼び出され、インターフェイスが再レンダリングされることになり、非常に非効率的になります。
- 最善の方法は、複数の更新を取得してからバッチ更新を実行することです。
- 状態が同期的に更新されても、レンダリング関数が実行されていない場合、状態とプロパティの同期を保つことができません。
- State と props は一貫性を維持できないため、開発中に多くの問題が発生します。
場合:
import React, {
Component } from "react";
function Hello(props) {
return <h2>{
props.message}</h2>;
}
export class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "Hello Zimo",
counter: 0,
};
}
changeText() {
this.setState({
message: "你好啊,紫陌" });
console.log(this.state.message);
}
increment() {
console.log("------");
//以下三个最终是counter:1
// this.setState({
// counter: this.state.counter + 1
// })
// this.setState({
// counter: this.state.counter + 1
// })
// this.setState({
// counter: this.state.counter + 1
// })
//以下结果是counter:3
// this.setState((state) => {
// return {
// counter: state.counter + 1
// }
// })
// this.setState((state) => {
// return {
// counter: state.counter + 1
// }
// })
// this.setState((state) => {
// return {
// counter: state.counter + 1
// }
// })
}
render() {
const {
message, counter } = this.state;
console.log("render被执行");
return (
<div>
<h2>message: {
message}</h2>
<button onClick={
(e) => this.changeText()}>修改文本</button>
<h2>当前计数: {
counter}</h2>
<button onClick={
(e) => this.increment()}>counter+1</button>
<Hello message={
message} />
</div>
);
}
}
8.3setState は必ず非同期ですか?
- React18以前
- 2 つの状況に分けられます。
- コンポーネントのライフサイクルまたは React 合成イベントでは、setState は非同期です。
- setTimeout またはネイティブ DOM イベントでは、setState が同期されます。
- React18以降
setState はデフォルトで非同期です
*React18 以降は、デフォルトですべての操作がバッチ処理 (非同期処理) になります。
コードを同期的に利用できるようにしたい場合は、特別な flashSync 操作を実行する必要があります。
import {
flushSync } from 'react-dom'
changeText() {
setTimeout(() => {
// 在react18之前, setTimeout中setState操作, 是同步操作
// 在react18之后, setTimeout中setState异步操作(批处理)
flushSync(() => {
this.setState({
message: "你好啊, 李银河" })
})
console.log(this.state.message)
}, 0);
}
9. レンダー関数の最適化(PureComponent,memo)
- アプリ内のデータが変更された場合、すべてのコンポーネントを再レンダリングし、差分アルゴリズムを実行する必要があります。パフォーマンスは非常に低くなりますが、多くのコンポーネントは再レンダリングする必要はありません。render を呼び出すには前提条件が必要です。つまり、依存するデータ (状態、プロパティ) が変更されたときに独自の render メソッドを呼び出す必要があります。
- render メソッドを呼び出すかどうかを制御するにはどうすればよいですか?
- shouldComponentUpdate メソッドを渡すだけです。
9.1 shouldコンポーネント更新
-
React は、ライフ サイクル メソッド shouldComponentUpdate (多くの場合、略して SCU と呼びます) を提供します。このメソッドはパラメータを受け取り、戻り値が必要です。
-
このメソッドには 2 つのパラメータがあります。
-
パラメータ 1: nextProps 変更後の最新の props 属性
-
パラメータ 2: nextState 変更後の最新の状態属性
-
このメソッドの戻り値はブール型です。
-
戻り値が true の場合は、render メソッドを呼び出す必要があります。
-
戻り値は false なので、render メソッドを呼び出す必要はありません。
-
デフォルトの戻り値は true です。これは、状態が変化する限り render メソッドが呼び出されることを意味します。
9.2ピュアコンポーネント
- すべてのクラスに対して shouldComponentUpdate を手動で実装する必要がある場合、開発者の作業負荷が増加します。
- shouldComponentUpdate におけるさまざまな判断の目的は何でしょうか?
- props または state のデータが変更されたかどうかによって、 shouldComponentUpdate が true を返すか false を返すかが決まります。
- 実際、React はこれをすでに考慮しているため、React はデフォルトでこれを実装しています。
- PureComponent からクラスを継承します。
9.3 上位成分メモ
- 機能コンポーネントの場合、プロパティが変更されていないときに DOM ツリー構造を再レンダリングすることは望ましくありません。
- 高次コンポーネントのメモを使用する必要があります。
場合
APP组件------------------------------------------------------------
import React, {
PureComponent } from 'react'
import Home from './Home'
import Recommend from './Recommend'
import Profile from './Profile'
export class App extends PureComponent {
constructor() {
super()
this.state = {
message: "Hello World",
counter: 0
}
}
// shouldComponentUpdate(nextProps, newState) {
// // App进行性能优化的点
// if (this.state.message !== newState.message || this.state.counter !== newState.counter) {
// return true
// }
// return false
// }
changeText() {
this.setState({
message: "你好啊,紫陌!" })
// this.setState({ message: "Hello World" })
}
increment() {
this.setState({
counter: this.state.counter + 1 })
}
render() {
console.log("App render")
const {
message, counter } = this.state
return (
<div>
<h2>App-{
message}-{
counter}</h2>
<button onClick={
e => this.changeText()}>修改文本</button>
<button onClick={
e => this.increment()}>counter+1</button>
<Home message={
message}/>
<Recommend counter={
counter}/>
<Profile message={
message}/>
</div>
)
}
}
export default App
Home组件----------------------------------------------------------------------------------------
import React, {
PureComponent } from 'react'
export class Home extends PureComponent {
constructor(props) {
super(props)
this.state = {
friends: []
}
}
// shouldComponentUpdate(newProps, nextState) {
// // 自己对比state是否发生改变: this.state和nextState
// if (this.props.message !== newProps.message) {
// return true
// }
// return false
// }
render() {
console.log("Home render")
return (
<div>
<h2>Home Page: {
this.props.message}</h2>
</div>
)
}
}
export default Home
Profile组件----------------------------------------------------------------------------------------------------
import {
memo } from "react"
const Profile = memo(function(props) {
console.log("profile render")
return <h2>Profile: {
props.message}</h2>
})
export default Profile
10. データは不変 (状態)
状態データを変更するには、PureComponent が render 関数を呼び出す前に、状態データをコピーして元のデータを置き換える必要があります。
import React, {
PureComponent } from "react";
export class App extends PureComponent {
constructor() {
super();
this.state = {
books: [
{
name: "你不知道JS", price: 99, count: 1 },
{
name: "JS高级程序设计", price: 88, count: 1 },
{
name: "React高级设计", price: 78, count: 2 },
{
name: "Vue高级设计", price: 95, count: 3 },
],
friend: {
name: "kobe",
},
message: "Hello World",
};
}
addNewBook() {
const newBook = {
name: "Angular高级设计", price: 88, count: 1 };
// 1.直接修改原有的state, 重新设置一遍
// 在PureComponent是不能重新渲染(render)
/* this.state.books.push(newBook)
this.setState({ books: this.state.books }) */
// 2.赋值一份books, 在新的books中修改, 设置新的books保证不是同一份books才会刷新render
const books = [...this.state.books];
books.push(newBook);
this.setState({
books: books });
}
addBookCount(index) {
// this.state.books[index].count++ 这个也不能重新渲染render
const books = [...this.state.books];
books[index].count++;
this.setState({
books: books });
}
render() {
const {
books } = this.state;
return (
<div>
<h2>数据列表</h2>
<ul>
{
books.map((item, index) => {
return (
<li key={
index}>
<span>
name:{
item.name}-price:{
item.price}-count:{
item.count}
</span>
<button onClick={
(e) => this.addBookCount(index)}>+1</button>
</li>
);
})}
</ul>
<button onClick={
(e) => this.addNewBook()}>添加新书籍</button>
</div>
);
}
}
export default App;
11.ref DOM の取得
- HTML 要素で ref 属性が使用される場合、コンストラクターで React.createRef() を使用して作成された ref は、基礎となる DOM 要素を現在の属性として受け取ります。
- ref 属性がカスタム クラス コンポーネントに使用される場合、ref オブジェクトはコンポーネントのマウントされたインスタンスを現在のプロパティとして受け取ります。
- 関数コンポーネントにはインスタンスがないため、関数コンポーネントでは ref 属性を使用できませんが、React.forwardRef を使用できます。
11.1ref DOM の取得
- 方法 1: 文字列を渡す
- 使用すると、対応する要素が this.refs で渡された文字列形式を通じて取得されます。
- 方法 2: オブジェクトを渡す
- オブジェクトは React.createRef() を通じて作成されます。
- 使用すると、作成されたオブジェクトの現在の属性の 1 つ (対応する要素) が取得されます。
- 方法 3: 関数を渡す
- この関数は、DOM がマウントされたときにコールバックします。この関数は要素オブジェクトを渡します。これは自分で保存できます。
- これを使用する場合は、以前に保存した要素オブジェクトを直接取得するだけです。
import React, {
PureComponent, createRef } from "react";
export class App extends PureComponent {
constructor() {
this.titleRef = createRef();
this.titleEl = null;
}
getNativeDOM() {
// 1.方式一: 在React元素上绑定一个ref字符串
// console.log(this.refs.why)
// 2.方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素
// console.log(this.titleRef.current)
// 3.方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入
console.log(this.titleEl);
}
render() {
return (
<div>
<h2 ref="why">Hello 紫陌</h2>
<h2 ref={
this.titleRef}>你好zimo</h2>
<h2 ref={
(el) => (this.titleEl = el)}>你好啊紫陌</h2>
<button onClick={
(e) => this.getNativeDOM()}>获取DOM</button>
</div>
);
}
}
export default App;
11.2 ref はクラスコンポーネントのインスタンスを取得します
import React, {
PureComponent, createRef } from "react";
class HelloWorld extends PureComponent {
test() {
console.log("test------");
}
render() {
return <h1>Hello World</h1>;
}
}
export class App extends PureComponent {
constructor() {
super();
this.hwRef = createRef();
}
getComponent() {
console.log(this.hwRef.current);
this.hwRef.current.test();
}
render() {
return (
<div>
<HelloWorld ref={
this.hwRef} />
<button onClick={
(e) => this.getComponent()}>获取组件实例</button>
</div>
);
}
}
export default App;
11.3 ref は関数グループのインスタンスを取得します
import React, {
PureComponent, createRef, forwardRef } from "react";
const HelloWorld = forwardRef(function (props, ref) {
return (
<div>
<h1 ref={
ref}>Hello World</h1>
<p>哈哈哈</p>
</div>
);
});
export class App extends PureComponent {
constructor() {
super();
this.hwRef = createRef();
}
getComponent() {
console.log(this.hwRef.current);
}
render() {
return (
<div>
<HelloWorld ref={
this.hwRef} />
<button onClick={
(e) => this.getComponent()}>获取组件实例</button>
</div>
);
}
}
export default App;
12. 制御されたコンポーネントと制御されていないコンポーネント
-
通常、フォーム要素は独自の状態を維持し、ユーザー入力に基づいて更新されます。
-
React では、変更可能な状態は通常、コンポーネントの state プロパティに保存され、setState() を使用してのみ更新できます。
-
この 2 つを組み合わせて、React の状態を「唯一のデータ ソース」にします; フォームをレンダリングする React コンポーネントは、ユーザー入力中にフォーム上で発生する操作も制御します。
-
このように値が React によって制御されるフォーム入力要素は「制御コンポーネント」と呼ばれます。
-
value 属性は form 要素に設定されているため、表示される値は常に this.state.value となり、React の state が唯一のデータ ソースになります。
-
Vue のような双方向バインディング
12.1 制御対象コンポーネント
class App extends PureComponent {
constructor(props) {
super();
this.state = {
name: "zimo",
};
}
inputChange(e) {
console.log(e.target.value);
this.setState({
name: e.target.value,
});
}
render() {
const {
name } = this.state;
return (
<div>
{
/* 受控组件 */}
<input type="text" value={
name} onChange={
(e) => this.inputChange(e)} />
<h2>{
name}</h2>
{
/* 非受控组件 */}
<input type="text" />
</div>
);
}
}
さまざまなデータ収集形式の例
class App extends PureComponent {
constructor(props) {
super();
this.state = {
username: "",
password: "",
isAGree: false,
hobbies: [
{
value: "sing", text: "唱", isChecked: false },
{
value: "dance", text: "跳", isChecked: false },
{
value: "rap", text: "rap", isChecked: false },
],
fruit: ["orange"],
};
}
handleSubmitClick(event) {
// 1.阻止默认行为
event.preventDefault();
// 2.获取到所有的表单数据, 对数据进行组织
console.log("获取所有的输入内容");
console.log(this.state.username, this.state.password);
const hobbies = this.state.hobbies.filter((item) => item.isChecked).map((item) => item.value);
console.log("获取爱好: ", hobbies);
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
handleInputChage(event) {
console.log(event);
this.setState({
[event.target.name]: event.target.value,
});
}
handleAgreeChange(event) {
this.setState({
isAGree: event.target.checked,
});
}
handleHobbiesChange(evnet, index) {
const hobbies = [...this.state.hobbies];
hobbies[index].isChecked = evnet.target.checked;
this.setState({
hobbies });
}
handleFruitChange(event) {
const options = Array.from(event.target.selectedOptions);
const values = options.map((item) => item.value);
this.setState({
fruit: values });
// 效果同上
const values2 = Array.from(event.target.selectedOptions, (item) => item.value);
console.log(values2);
}
render() {
const {
username, password, isAGree, hobbies, fruit } = this.state;
return (
<div>
{
/* 表单 */}
<form onSubmit={
(e) => this.handleSubmitClick(e)}>
{
/* 1.用户名密码 */}
<div>
<label htmlFor="username">
用户名:
<input
type="text"
id="username"
value={
username}
name="username"
onChange={
(e) => this.handleInputChage(e)}
/>
</label>
<label htmlFor="password">
密码:
<input
type="password"
id="password"
value={
password}
name="password"
onChange={
(e) => this.handleInputChage(e)}
/>
</label>
</div>
{
/* 2.checkbox 单选*/}
<label htmlFor="agree">
<input type="checkbox" id="agree" checked={
isAGree} onChange={
(e) => this.handleAgreeChange(e)} />
同意协议
</label>
{
/* 3.checkbox */}
<div>
你的爱好:
{
hobbies.map((item, index) => {
return (
<label htmlFor={
item.value} key={
item.value}>
<input
type="checkbox"
value={
item.value}
checked={
item.isChecked}
id={
item.value}
onChange={
(e) => this.handleHobbiesChange(e, index)}
/>
<span>{
item.text}</span>
</label>
);
})}
</div>
{
/* 4.select 多选*/}
<select value={
fruit} onChange={
(e) => this.handleFruitChange(e)} multiple>
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
{
/* 提交按钮 */}
<button type="submit">注册</button>
</form>
</div>
);
}
}
export default App;
12.2 制御されていないコンポーネント
- React では、ほとんどの場合、制御されたコンポーネントを使用してフォーム データを処理することをお勧めします。制御されたコンポーネントでは、フォーム データは React コンポーネントによって管理されます。
- もう 1 つの方法は、制御されていないコンポーネントを使用することです。この場合、フォーム データは DOM ノードによって処理されます。
- 制御されていないコンポーネントからのデータを使用したい場合は、ref を使用して DOM ノードからフォーム データを取得する必要があります。
- ref を使用して入力要素を取得します。これは通常、制御されていないコンポーネントのデフォルトを設定するために使用されます。
- 制御されていないコンポーネントの開発はあまり役に立ちません
import React, {
PureComponent, createRef } from "react";
class App extends PureComponent {
constructor(props) {
super();
this.state = {
intro: "紫陌yyds",
};
this.introRef = createRef();
}
handleSubmitClick(event) {
// 1.阻止默认行为
event.preventDefault();
// 2.获取到所有的表单数据, 对数据进行组织
console.log("ref", this.introRef.current.value);
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
render() {
const {
intro } = this.state;
return (
<div>
{
/* 非受控组件 */}
<input type="text" defaultValue={
intro} ref={
this.introRef} />
{
/* 提交按钮 */}
<button type="submit">注册</button>
</div>
);
}
}
13.高次成分
- 高階関数の定義は、次の条件の少なくとも 1 つを満たします。
- 1 つ以上の関数を入力として受け入れます。
- 関数を出力します。
- JavaScript でより一般的な filter、map、reduce 関数はすべて高階関数です。
- 高次コンポーネントの英語名は Higher-Order Components (略して HOC) です。
- 公式定義: 高次コンポーネントは、パラメータがコンポーネントであり、戻り値が新しいコンポーネントである関数です。
**上位コンポーネント自体はコンポーネントではなく関数です、この関数の引数もコンポーネント、戻り値もコンポーネントです**
13.1 高次コンポーネントの定義と機能
import React, {
PureComponent } from "react";
// 定义一个高阶组件
function hoc(Cpn) {
// 定义类组件
class NewCpn extends PureComponent {
render() {
return <Cpn name="zimo" />;
}
}
return NewCpn;
}
class Hello extends PureComponent {
render() {
return <div>你好啊,紫陌</div>;
}
}
const HelloHOC = hoc(Hello);
export class App extends PureComponent {
render() {
return (
<div>
<HelloHOC />
</div>
);
}
}
export default App;
13.2 高次コンポーネントの application-props の機能強化
APPコンポーネント
import React, {
PureComponent } from "react";
import enhancedUserInfo from "./hoc/enhanced_props";
import About from "./pages/About";
const Home = enhancedUserInfo(function (props) {
return (
<h1>
Home: {
props.name}-{
props.level}-{
props.banners}
</h1>
);
});
const Profile = enhancedUserInfo(function (props) {
return (
<h1>
Profile: {
props.name}-{
props.level}
</h1>
);
});
const HelloFriend = enhancedUserInfo(function (props) {
return (
<h1>
HelloFriend: {
props.name}-{
props.level}
</h1>
);
});
export class App extends PureComponent {
render() {
return (
<div>
<Home banners={
["轮播图1", "轮播图2"]} />
<Profile />
<HelloFriend />
<About />
</div>
);
}
}
export default App;
enhancedUserInfo 上位コンポーネント:
import {
PureComponent } from "react";
// 定义组件: 给一些需要特殊数据的组件, 注入props
function enhancedUserInfo(OriginComponent) {
class NewComponent extends PureComponent {
constructor(props) {
super(props);
this.state = {
userInfo: {
name: "zimo",
level: 99,
},
};
}
render() {
return <OriginComponent {
...this.props} {
...this.state.userInfo} />;
}
}
return NewComponent;
}
export default enhancedUserInfo;
コンポーネントについて
import React, {
PureComponent } from "react";
import enhancedUserInfo from "../hoc/enhanced_props";
export class About extends PureComponent {
render() {
return <div>About: {
this.props.name}</div>;
}
}
export default enhancedUserInfo(About);
13.3 上位コンポーネントアプリケーション - コンテキスト共有
コンテキストtheme_context.jsを定義する
import {
createContext } from "react";
const ThemeContext = createContext();
export default ThemeContext;
HOC関数を_theme.jsで定義する
import ThemeContext from "../context/theme_context";
function withTheme(OriginComponent) {
return (props) => {
return (
<ThemeContext.Consumer>
{
(value) => {
return <OriginComponent {
...value} {
...props} />;
}}
</ThemeContext.Consumer>
);
};
}
export default withTheme;
製品コンポーネント:
import React, {
PureComponent } from "react";
import withTheme from "../hoc/with_theme";
export class Product extends PureComponent {
render() {
const {
color, size } = this.props;
return (
<div>
<h2>
Product:{
color} - {
size}
</h2>
</div>
);
}
}
export default withTheme(Product);
アプリのコンポーネント:
import React, {
PureComponent } from "react";
import ThemeContext from "./context/theme_context";
import Product from "./pages/Product";
export class App extends PureComponent {
render() {
return (
<div>
<ThemeContext.Provider value={
{
color: "red", size: 50 }}>
<Product />
</ThemeContext.Provider>
</div>
);
}
}
export default App;
13.4 レンダリング判定認証
- 開発中には、次のようなシナリオに遭遇することがあります。
- 一部のページでは、ユーザーがログインに成功する必要があります。
- ユーザーが正常にログインに失敗した場合は、ログイン ページに直接ジャンプします。
- 高レベルのコンポーネントを使用して、認証操作を完了できます。
function loginAuth(OriginComponent) {
return (props) => {
// 从localStorage中获取token
const token = localStorage.getItem("token");
if (token) {
return <OriginComponent {
...props} />;
} else {
return <h2>请先登录, 再进行跳转到对应的页面中</h2>;
}
};
}
export default loginAuth;
13.5 ライフサイクルハイジャック
高階関数を使用してライフ サイクルをハイジャックし、ライフ サイクル中に独自のロジックを完成させることができます。
上位コンポーネントがレンダリング時間を計算する方法は次のとおりです。
import {
PureComponent } from "react";
function logRenderTime(OriginComponent) {
return class extends PureComponent {
UNSAFE_componentWillMount() {
this.beginTime = new Date().getTime();
}
componentDidMount() {
this.endTime = new Date().getTime();
const interval = this.endTime - this.beginTime;
console.log(`当前${
OriginComponent.name}页面花费了${
interval}ms渲染完成!`);
}
render() {
return <OriginComponent {
...this.props} />;
}
};
}
export default logRenderTime;
13.6 高次成分の意味
- 高次コンポーネントは、特定の React コードをよりエレガントに処理できます。
- 初期の React では、ミックスインを使用してコンポーネントを再利用する方法が提供されていましたが、現在は推奨されていません。
- ミックスインは相互に依存し、相互に結合する可能性があるため、コードのメンテナンスには役立ちません。
- 異なるミックスイン内のメソッドは互いに競合する可能性があります。
- ミックスインが多すぎると、コンポーネントの処理がさらに面倒になり、コンポーネントに関連する処理も行う必要があり、コードが雪だるま式に複雑になります。
- HOC には次のような欠陥もあります。
- HOC は元のコンポーネント上でラップまたはネストする必要があります。HOC が広範囲に使用されると、大量のネストが生成され、デバッグが非常に困難になります。
- HOC はプロップをハイジャックする可能性があり、合意に従わない場合には紛争が発生する可能性があります。
- Hooks の登場は画期的で、React 以前に存在していた多くの問題を解決します。
- たとえば、このポインティング問題、ホックネストの複雑さの問題などです。
14. ポータルの使用
- 場合によっては、レンダリングされたコンテンツを親コンポーネントから独立させたい場合や、現在マウントされている DOM 要素からも独立させたいことがあります (デフォルトでは、ルートの ID で DOM 要素にマウントされます)。
- Portal は、親コンポーネントの外側に存在する DOM ノードに子ノードをレンダリングするための優れたソリューションを提供します。
- 最初のパラメータ (子) は、要素、文字列、フラグメントなどのレンダリング可能な React 子要素です。
- 2 番目のパラメータ (コンテナ) は DOM 要素です。
createPortal(
アプリ H2
、 document.querySelector(“#zimo”))
index.html-------------------------------------------------------------------------------------------------
<div id="root"></div>
<div id="zimo"></div>
App组件:---------------------------------------------------------------------------------------------------
import React, {
PureComponent } from 'react'
import {
createPortal } from "react-dom"
export class App extends PureComponent {
render() {
return (
<div className='app'>
<h1>App H1</h1>
{
/*元素放到zimo容器 */}
{
createPortal(<h2>App H2</h2>, document.querySelector("#zimo"))
}
</div>
)
}
}
export default App
15.フラグメント
- 開発では、コンポーネントからコンテンツを返すときに常に div 要素をラップします。
- また、そのような div をレンダリングできず、Fragment (Vue のテンプレートと同様) を使用できないことを願っています。
- フラグメントを使用すると、DOM に追加のノードを追加せずにサブリストをグループ化できます。
- React は、Fragment の短い構文も提供します。
- 空のタグ <></>のように見えます。
- Fragment にキーを追加する必要がある場合、短い構文は使用できません
import React, {
PureComponent, Fragment } from 'react'
export class App extends PureComponent {
constructor() {
super()
this.state = {
sections: [
{
title: "哈哈哈", content: "我是内容, 哈哈哈" },
{
title: "呵呵呵", content: "我是内容, 呵呵呵" },
{
title: "嘿嘿嘿", content: "我是内容, 嘿嘿嘿" },
{
title: "嘻嘻嘻", content: "我是内容, 嘻嘻嘻" },
]
}
}
render() {
const {
sections } = this.state
return (
<>
<h2>我是App的标题</h2>
<p>我是App的内容, 哈哈哈哈</p>
<hr />
{
sections.map(item => {
return (
{
/*这里不能使用简写 因为key*/}
<Fragment key={
item.title}>
<h2>{
item.title}</h2>
<p>{
item.content}</p>
</Fragment>
)
})
}
</>
)
}
}
export default App
16.ストリクトモード
- StrictMode は、アプリケーション内の潜在的な問題を強調表示するために使用されるツールです。
- Fragment と同様、StrictMode は表示される UI をレンダリングしません。
- 子孫要素に対して追加のチェックと警告をトリガーします
- 厳密モードのチェックは開発モードでのみ実行され、運用ビルドには影響しません。
例:
- Strict モードはアプリケーションのどの部分でも有効にできます
- 厳密モードのチェックはプロファイル コンポーネントに対しては実行されません。
- ただし、Home とそのすべての子孫要素はチェックされます。
import React, {
PureComponent, StrictMode } from "react";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
export class App extends PureComponent {
render() {
return (
<div>
<StrictMode>
<Home />
</StrictMode>
<Profile />
</div>
);
}
}
export default App;
16.1 厳密モードでは何をチェックしますか?
- 安全でないライフサイクルを特定します。
- 古い ref API の使用
- .予期しない副作用がないか確認する
- このコンポーネントのコンストラクターは 2 回呼び出されます。
- これは厳密モードでの意図的な操作であり、ここで記述されたロジック コードの一部が複数回呼び出されたときに副作用があるかどうかを確認できます。
- 運用環境では、これが 2 回呼び出されることはありません。
- 非推奨の findDOMNode メソッドを使用する
- 以前の React API では、findDOMNode を通じて DOM を取得できました。
- 廃止されたコンテキスト API を検出する
- 初期の頃、Context は、静的属性を通じて Context オブジェクトのプロパティを宣言し、getChildContext を通じて Context オブジェクトを返すことによって使用されていましたが、この方法は現在推奨されません。
17. React遷移アニメーションの実装
- React はかつて、開発者にアニメーション プラグイン act-addons-css-transition-group を提供していましたが、これは後にコミュニティによって維持され、現在の React-transition-group を形成しました。
- コンポーネントの出入りアニメーションを簡単に実装できるライブラリですが、使用する場合は別途インストールが必要です。
- React-transition-group 自体は非常に小さいので、アプリケーションに大きな負担を与えることはありません。
npm
npm install 反応遷移グループ --save
糸
糸反応遷移グループを追加
17.1 反応遷移基の主成分
- 反応遷移グループには主に 4 つのコンポーネントが含まれています。
- 遷移
- このコンポーネントはプラットフォームに依存しないコンポーネントです (必ずしも CSS と組み合わせる必要はありません)。
- フロントエンド開発では通常、CSS を組み合わせてスタイルを完成させるため、CSSTransition がより一般的に使用されます。
- CSSトランジション
- フロントエンド開発では、通常、トランジション アニメーション効果を完成させるために CSSTransition が使用されます。
- スイッチトランジション
- このコンポーネントは、2 つのコンポーネントの表示と非表示を切り替えるときに使用されます。
- 移行グループ
- 複数のアニメーション コンポーネントをその中にラップします。通常、リスト内の要素のアニメーションに使用されます。
17.2 CSSTransition アニメーション
- CSSTransition は Transition コンポーネントに基づいて構築されています。
- CSSTransition の実行中には、出現、開始、終了の 3 つの状態があります。
- これらには 3 つの状態があり、対応する CSS スタイルを定義する必要があります。
- 最初のカテゴリ、開始状態: カテゴリは -open、-enter、exit です。
- 2 番目のカテゴリ:実行アニメーション: 対応するカテゴリは、-apper-active、-enter-active、-exit-active です。
- 3 番目のカテゴリ: **実行の終了: **対応するカテゴリは、-apper-done、-enter-done、-exit-done です。
- CSSTransition の共通対応プロパティ
- in: トリガーの開始または終了状態
- unmountOnExit={true}が追加された場合、コンポーネントは終了アニメーションの終了後に削除されます。
- in が trueの場合、エントリ状態がトリガーされ、アニメーションを開始するために -enter および -enter-acitve のクラスが追加されます。アニメーションの実行が終了すると、2 つのクラスが削除され、-enter- のクラスが追加されます。完了が追加されます。
- in が false の場合、終了状態がトリガーされ、アニメーションの実行を開始するために -exit クラスと -exit-active クラスが追加されます。アニメーションの実行が終了すると、2 つのクラスが削除され、-enter-done クラスが追加されます。追加されます。
- classNames: アニメーション クラスの名前
- css を記述するときに対応するクラス名を決定します。 zimoenter、zimo-enter-active、zimo-enter-done など。
- タイムアウト:
- トランジションアニメーション時間
- 現れる:
- 初めて入力するときにアニメーションを追加するかどうか (入力と同時に true である必要があります)
- unmountOnExit: 終了後にコンポーネントをアンインストールします
- 他の属性については、公式 Web サイトを参照して学習できます。 https://reactcommunity.org/react-transition-group/transition
- CSSTransition に対応するフック関数: 主にアニメーションの実行プロセスを検出し、一部の JavaScript 操作を完了します。
- onEnter: アニメーションに入る前にトリガーされます。
- onEntering: アプリケーションがアニメーションに入るとトリガーされます。
- onEntered: アプリケーションがアニメーションに入った後にトリガーされます。
アプリのコンポーネント:
import React, {
PureComponent } from "react";
import {
CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isShow: true,
};
}
render() {
const {
isShow } = this.state;
return (
<div>
<button onClick={
(e) => this.setState({
isShow: !isShow })}>切换</button>
{
/* 无动画 */}
{
/* {isShow && <h2>紫陌YYDS</h2>} */}
{
/* 有动画 */}
<CSSTransition
// 以下为属性
in={
isShow}
unmountOnExit={
true}
timeout={
2000}
classNames={
"zimo"}
appear
// 以下为钩子函数
onEnter={
(e) => console.log("开始进入动画")}
onEntering={
(e) => console.log("执行进入动画")}
onEntered={
(e) => console.log("执行进入结束")}
onExit={
(e) => console.log("开始离开动画")}
onExiting={
(e) => console.log("执行离开动画")}
onExited={
(e) => console.log("执行离开结束")}
>
<div className="section">
<h2>紫陌YYDS</h2>
<p>zimo学前端</p>
</div>
</CSSTransition>
</div>
);
}
}
export default App;
CSS:
.zimo-appear,
.zimo-enter {
opacity: 0;
}
.zimo-appear-active,
.zimo-enter-active {
opacity: 1;
transition: opacity 2s ease;
}
/* 离开动画 */
.zimo-exit {
opacity: 1;
}
.zimo-exit-active {
opacity: 0;
transition: opacity 2s ease;
}
17.3 SwitchTransition アニメーション
-
SwitchTransition は、2 つのコンポーネント間を切り替えるクールなアニメーションを完成させることができます。
- たとえば、オンとオフを切り替える必要があるボタンがあるとします。最初に左側からオンが終了し、右側からオフになってから入ることを確認したいとします。
- このアニメーションは、vue では vue トランジション モードと呼ばれます。
- SwitchTransition は、このアニメーションを実装するために、react-transition-group で使用されます。
-
SwitchTransition: mode にはメイン属性が 1 つあり、これには 2 つの値があります。
-
in-out: 新しいコンポーネントが最初に入力され、古いコンポーネントが削除されることを示します。
-
out-in: コンポーネントが最初に削除されてから、新しいコンポーネントが追加されることを意味します。
-
スイッチトランジションの使い方は?
-
SwitchTransition コンポーネントには CSSTransition または Transition コンポーネントが必要です。切り替えたいコンポーネントを直接ラップすることはできません。
-
SwitchTransition の CSSTransition または Transition コンポーネントは、以前のように要素の状態を決定するために in 属性を受け入れなくなり、代わりに key属性を使用します。
-
キー値が変更されると、CSSTransition コンポーネントが再レンダリングされ、アニメーションがトリガーされます。
アプリのコンポーネント:
import React, {
PureComponent } from "react";
import {
SwitchTransition, CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isLogin: true,
};
}
render() {
const {
isLogin } = this.state;
return (
<div>
<SwitchTransition mode="out-in">
<CSSTransition
// 当key值改变时,CSSTransition组件会重新渲染,也就会触发动画
key={
isLogin ? "login" : "exit"}
classNames={
"login"}
timeout={
1000}
>
<button onClick={
(e) => this.setState({
isLogin: !isLogin })}>{
isLogin ? "退出" : "登录"}</button>
</CSSTransition>
</SwitchTransition>
</div>
);
}
}
export default App;
CSS :
.login-enter {
transform: translateX(100px);
opacity: 0;
}
.login-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.login-exit {
transform: translateX(0);
opacity: 1;
}
.login-exit-active {
transform: translateX(-100px);
opacity: 0;
transition: all 1s ease;
}
17.4 TransitionGroup アニメーション
アニメーションのセットがある場合、アニメーションを完成させるためにこれらの CSSTransition を TransitionGroup に入れる必要があります。
アプリのコンポーネント:
import React, {
PureComponent } from "react";
import {
TransitionGroup, CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor() {
super();
this.state = {
books: [
{
id: 111, name: "你不知道JS", price: 99 },
{
id: 222, name: "JS高级程序设计", price: 88 },
{
id: 333, name: "Vuejs高级设计", price: 77 },
],
};
}
addNewBook() {
const books = [...this.state.books];
books.push({
id: new Date().getTime(),
name: "React高级程序设计",
price: 99,
});
this.setState({
books });
}
removeBook(index) {
const books = [...this.state.books];
books.splice(index, 1);
this.setState({
books });
}
render() {
const {
books } = this.state;
return (
<div>
<h2>书籍列表:</h2>
<TransitionGroup component="ul">
{
books.map((item, index) => {
return (
<CSSTransition key={
item.id} classNames="book" timeout={
1000}>
<li>
<span>{
item.name}</span>
<button onClick={
(e) => this.removeBook(index)}>删除</button>
</li>
</CSSTransition>
);
})}
</TransitionGroup>
<button onClick={
(e) => this.addNewBook()}>添加新书籍</button>
</div>
);
}
}
export default App;
CSS:
.book-enter {
transform: translateX(150px);
opacity: 0;
}
.book-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.book-exit {
transform: translateX(0);
opacity: 1;
}
.book-exit-active {
transform: translateX(150px);
opacity: 0;
transition: all 1s ease;
}
18. React の CSS
- CSS は React にとって常に悩みの種であり、多くの開発者が不満や批判を抱いている点でもあります。
- 現時点では、Vue の方が React よりも優れています。
- React は正式に React で統一されたスタイルを提供していません: 通常の CSS から CSS モジュール、JS の CSS に至るまで、数十の異なるソリューションと数百の異なるライブラリがあり、誰もがあなたにとって最適な CSS ソリューションを探しています。 、しかし今のところ統一された解決策はありません。
18.1 インラインスタイル
- インライン スタイルは、CSS スタイルの記述方法として公式に推奨されています。
- style は、CSS 文字列ではなく、キャメルケースの名前付きプロパティを使用する JavaScript オブジェクトを受け入れます。
- また、state で state を参照して、関連するスタイルを設定できます。
- インライン スタイルの利点:
- インライン スタイル。スタイル間に競合はありません。
- 現在の状態を動的に取得できる
- インライン スタイルの欠点:
- 文書ではキャメルケースを使用する必要があります。
- 一部のスタイルにはヒントがありません
- スタイルが多く、コードが複雑
- 一部のスタイルは記述できません(疑似クラス/疑似要素など)
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
size: 12,
};
}
addTitleSize() {
this.setState({
size: this.state.size + 2,
});
}
render() {
const {
size } = this.state;
return (
<div>
<button onClick={
(e) => this.addTitleSize()}>字体变大</button>
<h2 style={
{
fontSize: `${
size}px` }}>我是紫陌</h2>
<h2 style={
{
color: "yellowGreen" }}>我是紫陌</h2>
</div>
);
}
}
18.2 通常のCSS
- 通常、通常の CSS を別のファイルに記述し、後でインポートします。
- この記述方法は、通常の Web 開発の記述方法と一致しています。
- ただし、コンポーネント開発では、コンポーネントが独立したモジュールであることを常に望みます。スタイルであっても、それ自体内でのみ有効であり、相互に影響を与えることはありません。
- ただし、通常の CSS はグローバル CSS であり、スタイルは相互に影響します。
- この書き方の最大の問題は、スタイルが互いに重なり合うことです。
18.3 CSSモジュール
- css モジュールは React 固有のソリューションではありませんが、webpack と同様の構成を使用するあらゆる環境で使用できます。
- 他のプロジェクトで使用する場合は、webpack.config.js で module: true を設定するなど、自分で設定する必要があります。
- React のスキャフォールディングには、CSS モジュールの構成が組み込まれています。
- .css/.less/.scss などのスタイル ファイルは、.module.css/.module.less/.module.scss などに変更する必要があります。その後、引用して使用できます。
- CSS モジュールはローカル スコープの問題を解決し、多くの人が React で使用することを好むソリューションでもあります。
- ただし、このソリューションには独自の欠点もあります。
- 参照されるクラス名はコネクタ (.home-title) を使用できず、JavaScript では認識されません。
- すべてのclassName は、 {style.className} の形式で記述する必要があります。
- 特定のスタイルを動的に変更するのは不便なので、引き続きインライン スタイルを使用する必要があります。
- 上記の欠点が問題ないと思われる場合は、開発時に CSS モジュールを使用して記述することを選択できます。これは React で非常に一般的な方法でもあります。
ホーム.モジュール.css
.section {
border: 1px solid skyblue;
}
.title {
color: purple;
}
ホーム.jsx
import React, {
PureComponent } from "react";
import homeStyle from "./Home.module.css";
export class Home extends PureComponent {
render() {
return (
<div className={
homeStyle.section}>
<div className={
homeStyle.title}>Home的标题</div>
</div>
);
}
}
export default Home;
18.4 JS の CSS
-
公式ドキュメントには、JS ソリューションの CSS についても言及されています。
- 「CSS-in-JS」とは、CSS が外部ファイルで定義されるのではなく、JavaScript によって生成されるモードを指します。
- この機能はReact の一部ではなく、サードパーティのライブラリによって提供されることに注意してください。
- React にはスタイルの定義方法について明確なスタンスがありません。
-
従来のフロントエンド開発では、通常、構造 (HTML)、スタイル (CSS)、およびロジック (JavaScript) を分離します。
- React はロジック自体と UI を分離できないと考えているため、JSX 構文が存在します。
- CSS-in-JS モードは、スタイル (CSS) を JavaScript に記述する方法であり、JavaScript の状態を便利に使用できます。
- したがって、React はAll in JSと呼ばれます。
-
スタイル付きコンポーネントの紹介
- CSS-in-JS は JavaScript を使用して、スタイルの入れ子、関数定義、ロジックの再利用、動的状態の変更など、CSS プリプロセッサと同様の機能を CSS に提供します。CSS プリプロセッサにも特定のが、動的ステータスの取得は依然として困難です。扱いが難しい点。
- したがって、CSS-in-JS は現在、React で CSS を記述するための最も人気のあるソリューションであると言えます。
-
現在人気のある CSS-in-JS ライブラリ
- スタイル付きコンポーネント
- 感情
- グラマラス
styled-components は依然としてコミュニティで最も人気のある CSS-in-JS ライブラリであると言えます。
-
props、attrs 属性
-
小道具はスタイル付きコンポーネントに渡すことができます 小道具を取得するには、${} を通じて補間関数を渡す必要があります。小道具は関数のパラメーターとして使用されます。
-
この方法は、動的スタイルの問題を効果的に解決できます。
-
attrs属性を追加
-
export const SectionWrapper = styled.div.attrs((props) => ({ // 可以通过attrs给标签模板字符串中提供属性 tColor: props.color || "blue", }))` xxx xxx `;
-
-
場合
アプリのコンポーネント:
import React, {
PureComponent } from "react";
import {
AppWrapepr, SectionWrapper } from "./style";
export class App extends PureComponent {
constructor() {
super();
this.state = {
size: 30,
color: "yellow",
};
}
render() {
const {
size, color } = this.state;
return (
<AppWrapepr>
<SectionWrapper size={
size} color={
color}>
<h2 className="title">我是紫陌</h2>
<p className="content">我是zimo</p>
<button onClick={
(e) => this.setState({
color: "skyblue" })}>修改颜色</button>
</SectionWrapper>
<div className="footer">
<h2>这是底部</h2>
<p>zimo@yyds</p>
</div>
</AppWrapepr>
);
}
}
export default App;
style.js ファイル
import styled from "styled-components";
import {
primaryColor, largeSize } from "./style/variables";
// 基本使用
export const AppWrapepr = styled.div`
.footer {
border: solid 1px blue;
}
`;
// 子元素单独抽取到一个样式组件
export const SectionWrapper = styled.div.attrs((props) => ({
// 可以通过attrs给标签模板字符串中提供属性
tColor: props.color || "blue",
}))`
border: 1px solid red;
.title {
/* 可以接受外部传入的props */
font-size: ${
(props) => props.size}px;
color: ${
(props) => props.tColor};
&:hover {
background-color: purple;
}
}
/* 从一个单独文件中引入变量 */
.content {
font-size: ${
largeSize}px;
color: ${
primaryColor};
}
`;
variables.js ファイル (変数ファイル)
export const primaryColor = "#ff8822";
export const secondColor = "#ff7788";
export const smallSize = "12px";
export const middleSize = "14px";
export const largeSize = "18px";
- スタイル付きの高度な機能
styled はテーマを設定し、スタイルの継承をサポートします
Index.jsx (ルートコンポーネントはテーマ変数を渡します)
<ThemeProvider theme={
{
color: "purple", size: "50px" }}>
<App />
</ThemeProvider>
APP.jsx
import React, {
PureComponent } from "react";
import {
HomeWrapper, ButtonWrapper } from "./style";
export class Home extends PureComponent {
render() {
return (
<HomeWrapper>
<h2 className="header">商品列表</h2>
<ButtonWrapper>哈哈哈</ButtonWrapper>
</HomeWrapper>
);
}
}
export default Home;
スタイル.js
import styled from "styled-components";
const Button = styled.button`
border: 1px solid red;
border-radius: 5px;
`;
// 继承
export const ButtonWrapper = styled(Button)`
background-color: #0f0;
color: #fff;
`;
// 主题
export const HomeWrapper = styled.div`
.header {
color: ${
(props) => props.theme.color};
font-size: ${
(props) => props.theme.size};
}
`;
18.5 Reactにクラス(クラス名ライブラリ)を追加する
React は開発者に JSX の十分な柔軟性を提供し、JavaScript コードを記述するのと同じように、いくつかのロジックを通じて特定のクラスを追加するかどうかを決定できます。
クラス名を動的に追加するためのライブラリ
糸でクラス名を追加
import React, {
PureComponent } from "react";
import classNames from "classnames";
export class App extends PureComponent {
constructor() {
super();
this.state = {
isbbb: true,
isccc: true,
};
}
render() {
const {
isbbb, isccc } = this.state;
const classList = ["aaa"];
if (isbbb) classList.push("bbb");
if (isccc) classList.push("ccc");
const classname = classList.join(" ");
return (
<div>
{
/* 不推荐 */}
<h2 className={
`aaa ${
isbbb ? "bbb" : ""} ${
isccc ? "ccc" : ""}`}>我是紫陌</h2>
{
/* 不推荐 */}
<h2 className={
classname}>我是紫陌</h2>
{
/* 推荐 第三方库 */}
<h2 className={
classNames("aaa", {
bbb: isbbb, ccc: isccc })}>哈哈哈哈</h2>
<h2 className={
classNames(["aaa", {
bbb: isbbb, ccc: isccc }])}>呜呜呜</h2>
</div>
);
}
}
文法:
classNames(' foo','bar'); //=>'foo bar'
classNames('foo',{
bar: true });//=>·'foo bar'
classNames({
'foo-bar': false });//=>''
classNames({
foo: true },{
bar: true });//=>'foo bar'
classNames({
foo: true, bar: true }); //=>.'foo bar'
classNames('foo', {
bar: true, duck: false }, 'baz', {
quux: true }); //=>'foo bar baz quux'
classNames(null,false,'bar',undefined,0,1,{
baz:null}, '') // 'bar 1'
19. 純粋関数の理解
-
関数型プログラミングには純粋関数と呼ばれる非常に重要な概念があり、JavaScript は関数型プログラミングのパラダイムに準拠しているため、純粋関数の概念もあります。
-
React 開発では純粋関数について何度も言及されますが、例えば、React のコンポーネントは純粋関数のようであることが要求されます (クラス コンポーネントもあるのでなぜですか) Redux にはリデューサーという概念があり、それも要求されますそれは純粋関数でなければなりません。
-
純粋関数の簡単な要約:
- 決まった入力は必ず決まった出力を生み出します。
- この関数は実行中に副作用を引き起こすことはできません。
-
純粋関数の副作用の概念:
- 関数が実行されると、関数値を返すだけでなく、グローバル変数の変更、パラメーターの変更、外部ストレージの変更など、呼び出し元の関数に追加の影響を与えることを示します。
-
純粋関数の役割と利点
- 安心して書けて、安心して使える 関数の純度が保証されており、独自のビジネスロジックを実装するだけでよい 受信コンテンツの取得方法や他の外部変数を気にする必要がない変更されている; 使用されている 現時点では、入力内容が恣意的に改ざんされることはなく、決定した入力には確実に特定の出力が含まれることが保証されています。
- React では、関数であるかクラスであるかに関係なく、コンポーネントを宣言する必要があります。このコンポーネントは、プロパティが変更されないように保護する純粋な関数のようにする必要があります。リデューサーも純粋な関数である必要があります。
20. リダックス
20.1 Redux の 3 つの主要原則
- 単一のデータソース
- アプリケーション全体の状態はオブジェクト ツリーに保存されますが、このオブジェクト ツリーは1 つのストアにのみ保存されます。
- Reduxは複数の Store を作成することを強制しませんが、そうすることはデータのメンテナンスには役立ちません。
- 単一のデータ ソースにより、アプリケーション全体の状態の維持、追跡、変更が容易になります。
- 状態は読み取り専用です
- 状態を変更する唯一の方法は、アクションをトリガーすることです。他の方法で状態を変更しないでください。
- これにより、View リクエストもネットワーク リクエストも状態を直接変更できなくなり、アクションを使用して状態を変更する方法を記述することのみが可能になります。
- これにより、すべての変更が集中的に処理され、厳密な順序で実行されることが保証されるため、競合状態を心配する必要がなくなります。
- 純粋関数を使用して変更を実行する
- リデューサーを介して古い状態とアクションを接続し、新しい状態を返します。
- アプリケーションの複雑さが増すにつれて、リデューサーを複数の小さなリデューサーに分割して、それぞれ異なる状態ツリーの部分を操作することができます。
- ただし、すべてのリデューサーは純粋関数である必要があり、副作用を生成することはできません。
20.2 Redux を使用するプロセス
- この状態を保存するストアを作成します
- ストアの作成時にリデューサーを作成する必要があります。 -
- store.getState を通じて現在の状態を取得します。
- アクションを通じて状態を変更する
- ディスパッチによるディスパッチアクション。
- 通常、アクションには type 属性があり、他のデータも運ぶことができます。
- リデューサー内の処理コードを変更する
- reducer は純粋な関数であり、状態を直接変更する必要はありません。
- アクションをディスパッチする前にストアの変更を監視できます。
20.3Redux構造分割
- store/index.js ファイルを作成します。
- store/reducer.js ファイルを作成します。
- store/actionCreators.js ファイルを作成します。
- store/constants.js ファイルを作成します。
20.4 React を使用しない Redux コード
Redux と React の間に直接の関係はありませんが、Redux は React、Angular、Ember、jQuery、またはバニラ JavaScript で使用できます。
ストア/index.js
const {
createStore } = require("redux");
const reducer = require("./reducer.js");
// 创建的store
const store = createStore(reducer);
module.exports = store;
リデューサー.js
const {
ADD_NUMBER, CHANGE_NAME } = require("./constants");
// 初始化数据
const initialState = {
name: "张三",
counter: 100,
};
function reducer(state = initialState, action) {
switch (action.type) {
case CHANGE_NAME:
return {
...state, name: action.name };
case ADD_NUMBER:
return {
...state, counter: state.counter + action.num };
}
}
module.exports = reducer;
定数.js
const ADD_NUMBER = "add_number";
const CHANGE_NAME = "change_name";
module.exports = {
ADD_NUMBER,
CHANGE_NAME,
};
アクションクリエイター.js
const {
CHANGE_NAME, ADD_NUMBER } = require("./constants.js");
const changeNameAction = (name) => ({
type: CHANGE_NAME,
name,
});
const addNumberAction = (num) => ({
type: ADD_NUMBER,
num,
});
module.exports = {
changeNameAction,
addNumberAction,
};
上記4つのモジュールがストアの構造となります
コンポーネントがアクションをディスパッチする
const store = require("./store1/index.js");
const {
addNumberAction, changeNameAction } = require("./actionCreator.js");
const unsubscribe = store.subscribe(() => {
console.log("订阅数据的变化:", store.getSatte());
});
store.dispatch(changeNameAction("zimo"));
store.dispatch(changeNameAction("紫陌"));
store.dispatch(changeNameAction("yy"));
store.dispatch(addNumberAction(13));
store.dispatch(addNumberAction(167));
store.dispatch(addNumberAction(137));
20.5React の Redux
ストアディレクトリ構造のコードはスキップされます。。。。同上。コンポーネントを直接見る
アプリのコンポーネント:
import React, {
Component } from "react";
import Home from "./pages/home";
import Profile from "./pages/profile";
import "./style.css";
import store from "./store";
class App extends Component {
constructor() {
super();
this.state = {
counter: store.getState().counter,
};
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState();
this.setState({
counter: state.counter });
});
}
render() {
const {
counter } = this.state;
return (
<div>
<h2>App counter: {
counter}</h2>
<div className="pages">
<Home />
<Profile />
</div>
</div>
);
}
}
export default App;
ホームコンポーネント:
import React, {
PureComponent } from "react";
import store from "../store";
import {
addNumberAction } from "../store/actionCreators";
export class Home extends PureComponent {
constructor() {
super();
this.state = {
counter: store.getState().counter,
};
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState();
this.setState({
counter: state.counter });
});
}
addNumber(num) {
store.dispatch(addNumberAction(num));
}
render() {
const {
counter } = this.state;
return (
<div>
<h2>Home counterer: {
counter}</h2>
<div>
<button onClick={
(e) => this.addNumber(1)}>+1</button>
<button onClick={
(e) => this.addNumber(5)}>+5</button>
<button onClick={
(e) => this.addNumber(8)}>+8</button>
</div>
</div>
);
}
}
export default Home;
プロファイルコンポーネント
import React, {
PureComponent } from "react";
import store from "../store";
import {
addNumberAction } from "../store/actionCreators";
export class profile extends PureComponent {
constructor() {
super();
this.state = {
counter: store.getState().counter,
};
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState();
this.setState({
counter: state.counter });
});
}
addNumber(num) {
store.dispatch(addNumberAction(num));
}
render() {
const {
counter } = this.state;
return (
<div>
<h2>Profile Counter: {
counter}</h2>
<div>
<button onClick={
(e) => this.addNumber(1)}>+1</button>
<button onClick={
(e) => this.addNumber(5)}>+5</button>
<button onClick={
(e) => this.addNumber(8)}>+8</button>
</div>
</div>
);
}
}
export default profile;
以上が基本的な使い方です。しかし、問題があります。コンポーネントを作成するたびに、図に示すような一連の同様のロジックを作成する必要があります。
20.6 Redux – 高階関数の接続
-
高階関数の接続 実際、redux は、プロジェクト内で直接使用できる React-redux ライブラリの提供を公式にサポートしており、実装されたロジックはより厳密で効率的になります。
インストールreact-redux:yarn追加react-redux
-
Index.js ファイル内:コンテキストはストアをグローバルに挿入します
import App from "./App"; import { Provider } from "react-redux"; import store from "./store"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <Provider store={ store}> <App /> </Provider> );
-
コンポーネントについて: 加算および減算ロジックがあります。
import React, {
PureComponent } from "react";
import {
connect } from "react-redux";
import {
addNumberAction, subNumberAction } from "../store/actionCreators";
export class About extends PureComponent {
calcNumber(num, isAdd) {
if (isAdd) {
console.log("加", num);
this.props.addNumber(num);
} else {
console.log("减", num);
this.props.subNumber(num);
}
}
render() {
const {
counter } = this.props;
return (
<div>
<h2>About Page: {
counter}</h2>
<div>
<button onClick={
(e) => this.calcNumber(6, true)}>+6</button>
<button onClick={
(e) => this.calcNumber(88, true)}>+88</button>
<button onClick={
(e) => this.calcNumber(6, false)}>-6</button>
<button onClick={
(e) => this.calcNumber(88, false)}>-88</button>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
counter: state.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addNumber: (num) => {
dispatch(addNumberAction(num));
},
subNumber(num) {
dispatch(subNumberAction(num));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
- connect() 関数は 2 つのパラメーターを受け取る高階関数であり、connect 関数は高次コンポーネントを返します。最後の括弧はコンポーネント用です
- コネクト機能を使用します。mapStateToProps は、ストア上のデータを props にマップします。コンポーネントは props を通じて取得されます。アクションについても同様です。vueのmapState補助関数とmapAction補助関数に似ています
20.7 非同期アクションのディスパッチ
-
redux-thunk は非同期リクエストを送信できます**。デフォルトではdispatch(action)、アクションはJavaScriptオブジェクトである必要があります**。
-
redux-thunk はディスパッチ (アクション関数) でき、アクションは関数にすることができます。
-
関数が呼び出され、ディスパッチ関数と getState 関数がこの関数に渡されます。
-
ディスパッチ関数は、アクションを後で再度ディスパッチするために使用されます。
-
getState 関数は、後続の操作の一部が元の状態に依存する必要があることを考慮しており、以前の状態を取得できるようにするために使用されます。
-
redux-thunkの使用
糸追加 redux-thunk
import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducer from "./reducer.js"; const store = createStore(reducer, applyMiddleware(thunk)); export default store;
**ケース コード: 非同期アクション: **アクションは、ネットワーク リクエストを行うためにコンポーネント内の props を通じて呼び出されます。
import React, {
PureComponent } from "react";
import {
connect } from "react-redux";
import {
fetchHomeMultidataAction } from "../store/actionCreators";
export class Category extends PureComponent {
componentDidMount() {
this.props.fetchHomeMultidata();
}
render() {
return <h2>Category Page: {
this.props.counter}</h2>;
}
}
const mapStateToProps = (state) => {
return {
counter: state.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchHomeMultidata: () => {
dispatch(fetchHomeMultidataAction());
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Category);
actionCreators.js 中:
export const changeBannersAction = (banners) => ({
type: actionTypes.CHANGE_BANNERS,
banners,
});
export const changeRecommendsAction = (recommends) => ({
type: actionTypes.CHANGE_RECOMMENDS,
recommends,
});
export const fetchHomeMultidataAction = () => {
// 普通action
/*
如果是一个普通action,那么我们这里需要返回一个action对象
问题:对象是不能直接拿到服务器请求的异步数据的
return {}
*/
// 异步action处理返回函数
/*
如果返回一个函数那么redux是不支持的。需要借助插件redux-thunk
*/
return (dispatch, getState) => {
//异步操作
axios.get("http://xxxxxx/home/multidata").then((res) => {
const banners = res.data.data.banner.list;
const recommends = res.data.data.recommend.list;
// 派发action
dispatch(changeBannersAction(banners));
dispatch(changeRecommendsAction(recommends));
});
};
};
コンポーネントのmapStateToProps関数から直接取得できます。
20.8 redux-devtools
- ステップ 1: 対応するブラウザに関連するプラグインをインストールします (たとえば、Chrome ブラウザ拡張機能ストアで Redux DevTools を検索します)。
- ステップ 2: redux で devtools のミドルウェアを継承します。
import {
createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer.js";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
20.9 Reduxのモジュール性
**combineReducers**** 関数はモジュール化を実装します**
- combinReducers はどのように実装されますか?
- 実際、渡したレデューサーを object にマージし、最後に結合関数(前のリデューサー関数と同等) を返します。
- 組み合わせ関数の実行中に、前後に返されたデータが同じかどうかを判断して、前の状態を返すか新しい状態を返すかを決定します。
- 新しい状態はサブスクライバの対応するリフレッシュをトリガーしますが、古い状態はサブスクライバのリフレッシュを効果的に防ぐことができます。
ストア/index.js
import {
createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from "redux-thunk";
import counterReducer from "./counter";
import homeReducer from "./home";
// 将两个reducer合并在一起
const reducer = combineReducers({
counter: counterReducer,
home: homeReducer,
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
combinReducersの実装原理
import counterReducer from "./counter";
import homeReducer from "./home";
// combineReducers实现原理(了解)
function reducer(state = {
}, action) {
// 返回一个对象, store的state
return {
//state.counter 第一次undefined 就拿到默认值
counter: counterReducer(state.counter, action),
home: homeReducer(state.home, action),
};
}
21.Reduxツールキット
- Redux Toolkit は、Redux ロジックを作成するために公式に推奨される方法です。
- 以前 Redux について勉強したときに、Redux の記述ロジックが非常に煩雑で面倒であることがわかったはずです。
- そして、コードは通常、複数のファイルに分割されます (1 つのファイルで管理することもできますが、コードの量が多すぎるため、管理には役立ちません)。
- Redux Toolkit パッケージは、Redux ロジックを記述する標準的な方法になることを目的としており、それによって上記の問題を解決します。
- 多くの場所では、便宜上「RTK」とも呼ばれます。
Redux ツールキットをインストールします。
npm install @reduxjs/toolkit 反応-redux
- Redux Toolkit のコア API は主に次のとおりです。
- configureStore : createStore をラップして、簡略化された構成オプションと適切なデフォルトを提供します。スライス リデューサーを自動的に構成し、提供する Redux ミドルウェアを追加し、redux-thunk がデフォルトで含まれ、Redux DevTools Extension を有効にします。
- createSlice : リデューサー関数オブジェクト、スライス名、初期状態値を受け取り、対応するアクションを備えたスライス リデューサーを自動的に生成します。
- createAsyncThunk:アクション タイプの文字列と Promise を返す関数を受け入れ、Promise に基づいてアクション タイプをディスパッチする保留/履行/拒否されたサンクを生成します。
21.Redux Toolkit の使用プロセス
-
ストアの作成
- configureStore はストア オブジェクトの作成に使用されます。共通パラメータは次のとおりです。
- レデューサー。スライス内のレデューサーをオブジェクトに構成してここに渡すことができます。
- ミドルウェア: パラメーターを使用して他のミドルウェアに渡すことができます (詳細は自分で学習してください)。
- devTools: devTools ツールを構成するかどうか。デフォルトは true です。
import { configureStore } from "@reduxjs/toolkit"; import counterReducer from "./features/counter"; import homeReducer from "./features/home"; const store = configureStore({ reducer: { counter: counterReducer, home: homeReducer, }, }); export default store;
-
createSlice でスライスを作成します。
- createSlice には主に次のパラメータが含まれます。
- name : スライスをマークするためにユーザーが使用する名詞対応する名詞は後で redux-devtool に表示されます。
- InitialState : 初期化値、最初の初期化時の値。
- レデューサー: 前のレデューサー関数と同等
- オブジェクトタイプと多くの機能を追加できます。
- この関数は、redux の元のリデューサーの case ステートメントに似ています。
- 関数パラメータ:
- パラメータ 1: 状態
- パラメータ 2: このアクションを呼び出すときに渡されるアクション パラメータ。
- createSlice の戻り値は、すべてのアクションを含むオブジェクトです。
store/features/counter.js(カウンターのリデューサー)
import {
createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
counter: 999,
},
reducers: {
addNumber(state, {
payload }) {
console.log(payload);
state.counter = state.counter + payload;
},
subNumber(state, {
payload }) {
state.counter = state.counter - payload;
},
},
});
export const {
addNumber, subNumber } = counterSlice.actions;
export default counterSlice.reducer;
コンポーネント内のデータを表示: (この部分はホットリダックスと同じです)
import React, {
PureComponent } from "react";
import {
connect } from "react-redux";
import {
addNumber } from "../store/features/counter";
export class About extends PureComponent {
render() {
const {
counter } = this.props;
return (
<div>
<h2>About Page: {
counter}</h2>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
counter: state.counter.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addNumber: (num) => {
dispatch(addNumber(num));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
21.2 Toolkit の非同期操作
- 以前は、redux-thunk ミドルウェアにより、ディスパッチ時に非同期操作を実行できました。
- Redux Toolkit はデフォルトで Thunk 関連の関数を継承しています: createAsyncThunk
createAsyncThunk によって作成されたアクションがディスパッチされると、次の 3 つの状態になります。
- pending: アクションは発行されましたが、最終的な結果はまだありません。
- 満たされた: 最終結果を取得します (戻り値のある結果)。
- 拒否されました: 実行中にエラーが発生したか、例外がスローされました。
import {
createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
// 1.
export const fetchHomeMultidataAction = createAsyncThunk("fetch/homemultidata", async () => {
const res = await axios.get("http://123.207.32.32:8000/home/multidata");
return res.data;
});
const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
recommends: [],
},
reducers: {
changeBanners(state, {
payload }) {
state.banners = payload;
},
changeRecommends(state, {
payload }) {
state.recommends = payload;
},
},
//2.
extraReducers: {
[fetchHomeMultidataAction.pending](state, action) {
console.log("fetchHomeMultidataAction pending");
},
[fetchHomeMultidataAction.fulfilled](state, {
payload }) {
state.banners = payload.data.banner.list;
state.recommends = payload.data.recommend.list;
},
[fetchHomeMultidataAction.rejected](state, action) {
console.log("fetchHomeMultidataAction rejected");
},
},
});
export const {
changeBanners, changeRecommends } = homeSlice.actions;
export default homeSlice.reducer;
extraReducerの2番目の書き方(関数型の書き方)
ケースをビルダーに追加して、非同期操作の結果を監視できます。
extraReducers: (builder) => {
builder
.addCase(fetchHomeMultidataAction.pending, (state, action) => {
console.log("fetchHomeMultidataAction-pending");
})
.addCase(fetchHomeMultidataAction.fulfilled, (state, {
payload }) => {
state.banners = payload.data.banner.list;
state.recommends = payload.data.recommend.list;
});
};
関数型記述を使用する別の方法もあります
22.リアクトルーター
-
インストール時に、react-router-dom を選択します。
-
反応ルーターには、Web 開発には必要のない反応ネイティブ コンテンツが含まれます。
npm インストール反応ルーターダム
22.1 BrowserRouter または HashRouter
- BrowserRouter は履歴モードを使用します。
- HashRouter はハッシュ モードを使用します。
root.render(
<HashRouter>
<App />
</HashRouter>
);
22.2 ルートマッピングの設定
- ルート: すべてのルートをラップし、その中のルートと一致します。
- Router5.x は Switch コンポーネントを使用します
- ルート: ルートはパスのマッチングに使用されます。
- path 属性: 一致したパスを設定するために使用されます。
- element 属性: パスと一致した後にレンダリングされるコンポーネントを設定します。
- Router5.x はコンポーネント属性を使用します
- 正確: 完全一致 パスが完全に一致する場合にのみ、対応するコンポーネントがレンダリングされます。
- Router6.x はこのプロパティをサポートしなくなりました
<Routes>
<Route path="/home" element={
<Home />}>
<Route path="/home/recommend" element={
<HomeRecommend />} />
</Route>
</Routes>
22.3 ルートジャンプ
- リンク
to 属性: Link で最も重要な属性。ジャンプ先のパスを設定するために使用されます。
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
- ナビリンク
要件: パスが選択されると、対応する a 要素が赤色に変わります
- Link コンポーネントの代わりに NavLink コンポーネントを使用します。
- style: 関数を渡します。関数は isActive 属性を含むオブジェクトを受け入れます。
- className: 関数を渡します。関数は isActive 属性を含むオブジェクトを受け入れます。
<NavLink to="/home" style={
({
isActive}) => ({
color: isActive ? "red": ""})}>首页</NavLink>
<NavLink to="/about" style={
({
isActive}) => ({
color: isActive ? "red": ""})}>关于</NavLink>
デフォルトの activeClassName: 実際、デフォルトの一致が成功すると、NavLink は動的アクティブ クラスを追加するため、スタイルを直接記述することもできます。
このクラスが他の場所で使用されており、スタイルがカスケードされていることが心配な場合は、クラスをカスタマイズすることもできます。
<NavLink to="/home" className={
({
isActive}) => isActive?"link-active":""}>首页</NavLink>
<NavLink to="/about" className={
({
isActive}) => isActive?"link-active":""}>关于</NavLink>
-
ナビゲート
Navigate はルート リダイレクトに使用されます。このコンポーネントが表示されると、対応するパスにジャンプします。
<Route path="/" element={<Navigate to="/home" />} />
23.4「見つかりません」ページの構成
- Not Found ページを作成します。
- 対応するルートを構成し、パスを * に設定します。
<Route path="*" element={
<NotFound />} />
23.5 ルートのネスト
<Routes>
<Route path="/home" element={
<Home />}>
<Route path="/home" element={
<Navigate to="/home/recommend" />} />
<Route path="/home/recommend" element={
<HomeRecommend />} />
<Route path="/home/ranking" element={
<HomeRanking />} />
</Route>
</Routes>
23.6 手動配線ジャンプ
- ジャンプは主に Link または NavLink を通じて行われますが、実際には JavaScript コードを通じてジャンプすることもできます。
- Navigate コンポーネントはルーティング ジャンプを実行できますが、それでもコンポーネントです。
- JavaScript コードのロジック (ボタンのクリックなど) をジャンプしたい場合は、navigate オブジェクトを取得する必要があります。
-
フックを使ったファンクショナルジャンプ
export function App(props) { const navigate = useNavigate(); function navigateTo(path) { navigate(path); } return ( <div className="app"> <div className="nav"> <button onClick={ (e) => navigateTo("/category")}>分类</button> <span onClick={ (e) => navigateTo("/order")}>订单</span> </div> <div className="content"> <Routes> <Route path="/category" element={ <Category />} /> <Route path="/order" element={ <Order />} /> </Routes> </div> </div> ); }
-
クラス ルート ジャンプ (上位コンポーネントを使用してカプセル化する必要があり、最初のポイントが実装されます)
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"; // 高阶组件: 函数 function withRouter(WrapperComponent) { return function (props) { // 1.导航 const navigate = useNavigate(); // 2.动态路由的参数: /detail/:id const params = useParams(); // 3.查询字符串的参数: /user?name=why&age=18 const location = useLocation(); const [searchParams] = useSearchParams(); const query = Object.fromEntries(searchParams); const router = { navigate, params, location, query }; return <WrapperComponent { ...props} router={ router} />; }; } export default withRouter;
いつ使用するか:
import { withRouter } from "../hoc"; export class Home extends PureComponent { navigateTo(path) { const { navigate } = this.props.router; navigate(path); } render() { return ( <div> <button onClick={ (e) => this.navigateTo("/home/songmenu")}>歌单</button> { /* 占位的组件 */} <Outlet /> </div> ); } } export default withRouter(Home);
23.7 ルーティングパラメータの受け渡し
パラメータを渡すには 2 つの方法があります。
- 動的ルーティング方式。
- 検索ではパラメータが渡されます。
- たとえば、/detail のパスはコンポーネントの Detail に対応します。
- Route と一致するときにパスを /detail/:id として記述すると、/detail/abc と /detail/123 が Route と一致して表示されます。
- この一致ルールを動的ルーティングと呼びます。
- 動的ルーティングを使用してルートのパラメータを渡す
- 検索パスパラメータ
高レベルのコンポーネント:
import {
useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
// 高阶组件: 函数
function withRouter(WrapperComponent) {
return function (props) {
// 1.导航
const navigate = useNavigate();
// 2.动态路由的参数: /detail/:id
const params = useParams();
// 3.查询字符串的参数: /user?name=why&age=18
const location = useLocation();
const [searchParams] = useSearchParams();
const query = Object.fromEntries(searchParams);
const router = {
navigate, params, location, query };
return <WrapperComponent {
...props} router={
router} />;
};
}
export default withRouter;
- 検索パスパラメータ
23.8 ルーティング設定ファイル (ルートの遅延ロードを含む)
- 現在、ルーティングの定義は、Route コンポーネントを使用し、属性を追加することによって直接行われます。
- しかし、このアプローチではルーティングが非常に複雑になるため、すべてのルーティング設定を 1 か所にまとめて集中管理したいと考えています。
- 初期の頃、Router は関連する API を提供していなかったので、完了するには、react-router-config を使用する必要がありました。
- Router6.x では、関連する設定を完了するための useRoutes API が提供されています。
<div className="content">{
useRoutes(routes)}</div>
vueに似ている
ルーティングテーブル:
import Home from "../pages/Home";
import HomeRecommend from "../pages/HomeRecommend";
import HomeRanking from "../pages/HomeRanking";
import HomeSongMenu from "../pages/HomeSongMenu";
// import About from "../pages/About"
// import Login from "../pages/Login"
import Category from "../pages/Category";
import Order from "../pages/Order";
import NotFound from "../pages/NotFound";
import Detail from "../pages/Detail";
import User from "../pages/User";
import {
Navigate } from "react-router-dom";
import React from "react";
const About = React.lazy(() => import("../pages/About"));
const Login = React.lazy(() => import("../pages/Login"));
const routes = [
{
path: "/",
element: <Navigate to="/home" />,
},
{
path: "/home",
element: <Home />,
children: [
{
path: "/home",
element: <Navigate to="/home/recommend" />,
},
{
path: "/home/recommend",
element: <HomeRecommend />,
},
{
path: "/home/ranking",
element: <HomeRanking />,
},
{
path: "/home/songmenu",
element: <HomeSongMenu />,
},
],
},
{
path: "/about",
element: <About />,
},
{
path: "/login",
element: <Login />,
},
{
path: "/category",
element: <Category />,
},
{
path: "/order",
element: <Order />,
},
{
path: "/detail/:id",
element: <Detail />,
},
{
path: "/user",
element: <User />,
},
{
path: "*",
element: <NotFound />,
},
];
export default routes;
ルートの遅延読み込み:
23.フック
- フックを要約すると、次のようになります。
- これにより、クラスを作成せずに状態やその他の React 機能を使用できるようになります。
- しかし、これをさまざまな用途に拡張して、前述した問題を解決することができます。
- フックの使用シナリオ:
- Hook の出現は、基本的に、以前にクラス コンポーネントを使用していたすべての場所を置き換えることができます。
- ただし、古いプロジェクトの場合は、完全に下位互換性があり、段階的に使用できるため、すべてのコードをフックに直接リファクタリングする必要はありません。
- フックは関数コンポーネント内でのみ使用でき、クラス コンポーネントや関数コンポーネント外では使用できません。
- フックは次のとおりであることを覚えておいてください。
- 完全にオプション: 既存のコードを書き直すことなく、一部のコンポーネントでフックを試すことができます。ただし、そうしたくない場合は、今すぐフックを学習したり使用したりする必要はありません。
- 100% 下位互換性: フックには重大な変更は含まれていません。
- 現在利用可能: フックは v16.8.0 でリリースされました。
23.1 useStateフック
-
パラメータ: 初期化値 (未定義に設定されていない場合)。
-
戻り値: 2 つの要素を含む配列。
- 要素 1: 現在の状態の値 (最初の呼び出しは初期化値です)。
- 要素 2: ステータス値を設定する関数。
-
useState の戻り値は配列なので、配列を分割することで代入が完了するので非常に便利です。
-
1 つのコンポーネントで複数の変数や複合変数 (配列、オブジェクト) を定義することもできます。
-
これらを使用するには、さらに 2 つのルールがあります。
- フックは関数の最も外側のレベルでのみ呼び出すことができます。ループ、条件判定、サブ関数内で呼び出さないでください。
- フックは React 関数コンポーネント内でのみ呼び出すことができます。他の JavaScript 関数内で呼び出さないでください。
import React, {
memo, useState } from "react";
const App = memo(() => {
const [message, setMessage] = useState("zimo");
function changeMessage() {
setMessage("紫陌");
}
return (
<div>
<h2>App:{
message}</h2>
<button onClick={
changeMessage}>修改文本</button>
</div>
);
});
export default App;
23.1 useEffectフック
- useEffect フックを使用すると、クラス内のライフサイクル関数と同様のいくつかのライフサイクル関数を完了できます。
- ネットワーク リクエスト、手動 DOM 更新、一部のイベント モニタリングと同様に、これらはすべて React による DOM 更新の副作用です。
- したがって、これらの機能を完了するフックはエフェクト フックと呼ばれます。
- 使用効果の分析
- useEffect のフックを通じて、レンダリング後に特定の操作を実行する必要があることを React に伝えることができます。
- useEffect では、コールバック関数を渡す必要があります。この関数は、React が DOM 更新操作を完了した後にコールバックされます。
- デフォルトでは、このコールバック関数は、最初のレンダリングの後、**、または各更新の後であるかどうかに関係なく実行されます。
- 効果をクリアする必要があります
- クラスコンポーネントの作成プロセス中に、componentWillUnmount 内のいくつかの副作用コードをクリアする必要があります。
- たとえば、以前のイベント バスまたは Redux では、subscribe を手動で呼び出しました。
- すべては、componentWillUnmount で対応するサブスクリプション解除を行う必要があります。
Effect で関数を返すのはなぜでしょうか?
- これはエフェクトのオプションのクリーンアップ メカニズムです。各エフェクトはクリーンアップ関数を返すことができます。
- これにより、サブスクリプションの追加と削除のロジックをまとめることができます。
- それらはすべて効果の一部です。
React の効果はいつ解除されますか?
- React は、コンポーネントが更新およびアンインストールされるときにクリーンアップ操作を実行します。
- エフェクトはレンダリングされるたびに実行されます。
import React, {
memo, useEffect, useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(0);
// 负责告知React,在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
console.log("监听redux中数据变化, 监听eventBus中的zimo事件");
// 返回值:回调函数 => 组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("取消监听redux中数据变化, 取消监听eventBus中的zimo事件");
};
});
return (
<div>
<button onClick={
(e) => setCount(count + 1)}>+1({
count})</button>
</div>
);
});
export default App;
- 複数の効果
- エフェクト フックを使用すると、それらを異なる useEffect に分割できます。
- React は、コンポーネント内の各エフェクトを、エフェクトが宣言された順序で順番に呼び出します。
import React, {
memo, useEffect } from "react";
import {
useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(0);
// 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
// 1.修改document的title(1行)
console.log("修改title");
});
// 一个函数式组件中, 可以存在多个useEffect
useEffect(() => {
// 2.对redux中数据变化监听(10行)
console.log("监听redux中的数据");
return () => {
// 取消redux中数据的监听
};
});
useEffect(() => {
// 3.监听eventBus中的why事件(15行)
console.log("监听eventBus的zimo事件");
return () => {
// 取消eventBus中的zimo事件监听
};
});
return (
<div>
<button onClick={
(e) => setCount(count + 1)}>+1({
count})</button>
</div>
);
});
export default App;
- エフェクトパフォーマンスの最適化
- デフォルトでは、useEffect のコールバック関数はレンダリングされるたびに再実行されますが、これにより次の 2 つの問題が発生します。
- 一部のコードは、componentDidMount およびcomponentWillUnmount で実行されるものと同様に、1 回だけ実行する必要があります (ネットワーク リクエスト、サブスクリプション、サブスクリプション解除など)。
- 複数回実行すると、特定のパフォーマンスの問題も発生します。
useEffect は実際には 2 つのパラメータを取ります。
- パラメータ1:実行するコールバック関数
- パラメータ 2: useEffect は状態が変化した場合にのみ再実行されます (誰が影響を受けるか)
関数の内容に依存したくない場合は、空の配列 [] を渡すこともできます。
- ここでの 2 つのコールバック関数は、それぞれ、componentDidMount ライフサイクル関数とcomponentWillUnmount ライフサイクル関数に対応します。
import React, {
memo, useEffect } from "react";
import {
useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(0);
const [message, setMessage] = useState("Hello World");
useEffect(() => {
console.log("修改title:", count);
}, [count]);
useEffect(() => {
console.log("监听redux中的数据");
return () => {
};
}, []);
useEffect(() => {
console.log("监听eventBus的why事件");
return () => {
};
}, []);
useEffect(() => {
console.log("发送网络请求, 从服务器获取数据");
return () => {
console.log("会在组件被卸载时, 才会执行一次");
};
}, []);
return (
<div>
<button onClick={
(e) => setCount(count + 1)}>+1({
count})</button>
<button onClick={
(e) => setMessage("你好啊")}>修改message({
message})</button>
</div>
);
});
export default App;
23.3 useContext
コンポーネントで共有コンテキストを使用するには 2 つの方法があります
- クラス コンポーネントは、class name.contextType = MyContext; を通じてクラス内のコンテキストを取得できます。
- 複数のコンテキスト、または機能コンポーネント内の MyContext.Consumer を介したコンテキストの共有。
ただし、複数のコンテキストが共有される場合、多くのネストが発生します。
- Context Hook を使用すると、フックを通じて Context の値を直接取得できます。
index.js-------------------------------------------------------------------------------
root.render(
<UserContext.Provider value={
{
name: "zimo", level: 99}}>
<ThemeContext.Provider value={
{
color:"red",size:18}}>
<App />
</ThemeContext.Provider>
</UserContext.Provider>
)
函数组件中---------------------------------------------------------------------------
import React, {
memo, useContext } from 'react'
import {
UserContext, ThemeContext } from "./context"
const App = memo(() => {
// 使用Context
const user = useContext(UserContext)
const theme = useContext(ThemeContext)
return (
<div>
<h2>User: {
user.name}-{
user.level}</h2>
<h2 style={
{
color: theme.color, fontSize: theme.size}}>Theme</h2>
</div>
)
})
export default App
コンポーネントの上位層が最近更新された場合、このフックは再レンダリングをトリガーし、MyContext プロバイダーに渡された最新のコンテキスト値を使用します。
23.4 useReducer
- useReducer を見た多くの人は、これが redux の代わりになるのではないかと最初に反応しますが、そうではありません。
- useReducer は useState の単なる代替手段です。
- 一部のシナリオでは、状態処理ロジックが比較的複雑な場合は、useReducer を通じて分割できます。
- または、変更された状態が以前の状態に依存する必要がある場合にも使用できます。
コメントはuseState書き込みメソッドです
import React, {
memo, useReducer } from "react";
// import { useState } from 'react'
function reducer(state, action) {
switch (action.type) {
case "increment":
return {
...state, counter: state.counter + 1 };
case "decrement":
return {
...state, counter: state.counter - 1 };
case "add_number":
return {
...state, counter: state.counter + action.num };
case "sub_number":
return {
...state, counter: state.counter - action.num };
default:
return state;
}
}
// useReducer+Context => redux
const App = memo(() => {
// const [count, setCount] = useState(0)
const [state, dispatch] = useReducer(reducer, {
counter: 0, friends: [], user: {
} });
// const [counter, setCounter] = useState()
// const [friends, setFriends] = useState()
// const [user, setUser] = useState()
return (
<div>
{
/* <h2>当前计数: {count}</h2>
<button onClick={e => setCount(count+1)}>+1</button>
<button onClick={e => setCount(count-1)}>-1</button>
<button onClick={e => setCount(count+5)}>+5</button>
<button onClick={e => setCount(count-5)}>-5</button>
<button onClick={e => setCount(count+100)}>+100</button> */}
<h2>当前计数: {
state.counter}</h2>
<button onClick={
(e) => dispatch({
type: "increment" })}>+1</button>
<button onClick={
(e) => dispatch({
type: "decrement" })}>-1</button>
<button onClick={
(e) => dispatch({
type: "add_number", num: 5 })}>+5</button>
<button onClick={
(e) => dispatch({
type: "sub_number", num: 5 })}>-5</button>
<button onClick={
(e) => dispatch({
type: "add_number", num: 100 })}>+100</button>
</div>
);
});
export default App;
23.5 useCallback
useCallback の実際の目的は、パフォーマンスを最適化することです。
- useCallback は関数のメモ化された値を返します。
- 依存関係が変更されていない場合、複数回定義しても戻り値は同じになります。
- 通常、useCallback を使用する目的は、サブコンポーネントを複数回レンダリングすることではなく、関数をキャッシュすることでもありません。
import React, {
memo, useState, useCallback, useRef } from "react";
// useCallback性能优化的点:
// 1.当需要将一个函数传递给子组件时, 最好使用useCallback进行优化, 将优化之后的函数, 传递给子组件
// props中的属性发生改变时, 组件本身就会被重新渲染
const Home = memo(function (props) {
const {
increment } = props;
console.log("Home被渲染");
return (
<div>
<button onClick={
increment}>increment+1</button>
{
/* 100个子组件 */}
</div>
);
});
const App = memo(function () {
const [count, setCount] = useState(0);
const [message, setMessage] = useState("hello");
// 闭包陷阱: useCallback
// const increment = useCallback(function foo() {
// console.log("increment")
// setCount(count+1)
// }, [count])
// 进一步的优化: 当count发生改变时, 也使用同一个函数(了解)
// 做法一: 将count依赖移除掉, 缺点: 闭包陷阱
// 做法二: useRef, 在组件多次渲染时, 返回的是同一个值
const countRef = useRef();
countRef.current = count;
const increment = useCallback(function foo() {
console.log("increment");
setCount(countRef.current + 1);
}, []);
// 普通的函数
// const increment = () => {
// setCount(count+1)
// }
return (
<div>
<h2>计数: {
count}</h2>
<button onClick={
increment}>+1</button>
<Home increment={
increment} />
<h2>message:{
message}</h2>
<button onClick={
(e) => setMessage(Math.random())}>修改message</button>
</div>
);
});
export default App;
子コンポーネントに関数を渡す必要がある場合は、最適化のために useCallback を使用し、最適化された関数を子コンポーネントに渡すのが最善です。
最適化はサブコンポーネントを渡すときに反映されます。親コンポーネントを再レンダリングすると関数が再定義されますが、最適化は反映されません。したがって、それはサブコンポーネントに反映されます。関数は子コンポーネントに渡され、子コンポーネントを再レンダリングしても親コンポーネントの関数は再定義されません。
23.6 使用メモ
- useMemo の実際の目的は、パフォーマンスを最適化することでもあります。
- useMemo はメモ化された値も返します。
- 依存関係が変更されていない場合、複数回定義しても戻り値は同じになります。
import React, {
memo } from "react";
import {
useMemo, useState } from "react";
const HelloWorld = memo(function (props) {
console.log("HelloWorld被渲染~");
return <h2>Hello World</h2>;
});
function calcNumTotal(num) {
// console.log("calcNumTotal的计算过程被调用~")
let total = 0;
for (let i = 1; i <= num; i++) {
total += i;
}
return total;
}
const App = memo(() => {
const [count, setCount] = useState(0);
// const result = calcNumTotal(50)
// 1.不依赖任何的值, 进行计算
const result = useMemo(() => {
return calcNumTotal(50);
}, []);
// 2.依赖count
// const result = useMemo(() => {
// return calcNumTotal(count*2)
// }, [count])
// 3.使用useMemo对子组件渲染进行优化
const info = useMemo(() => ({
name: "why", age: 18 }), []);
return (
<div>
<h2>计算结果: {
result}</h2>
<h2>计数器: {
count}</h2>
<button onClick={
(e) => setCount(count + 1)}>+1</button>
<HelloWorld result={
result} info={
info} />
</div>
);
});
export default App;
useMemo は子コンポーネントに値を渡します。参照渡し型のみが最適化されます。{} []。通常の値は最適化されていません。オブジェクトと配列が再宣言されるためです。
23.7 useRef
- 使用法 1: DOM (またはコンポーネント、ただしクラスコンポーネントである必要があります) 要素を導入します。
import React, {
memo, useRef } from "react";
const App = memo(() => {
const titleRef = useRef();
const inputRef = useRef();
function showTitleDom() {
console.log(titleRef.current);
inputRef.current.focus();
}
return (
<div>
<h2 ref={
titleRef}>Hello,zimo</h2>
<input type="text" ref={
inputRef} />
<button onClick={
showTitleDom}>查看title的DOM</button>
</div>
);
});
export default App;
- 使用法 2: データの一部を保存する このオブジェクトは、ライフ サイクルを通じて変更されないまま維持できます。
import React, {
memo, useRef, useState, useCallback } from "react";
let obj = null;
const App = memo(() => {
const [count, setCount] = useState(0);
const nameRef = useRef();
console.log(obj === nameRef); //true
obj = nameRef;
// 通过useRef解决闭包陷阱
const countRef = useRef();
countRef.current = count;
const increment = useCallback(() => {
setCount(countRef.current + 1);
}, []);
return (
<div>
<h2>Hello World: {
count}</h2>
<button onClick={
(e) => setCount(count + 1)}>+1</button>
<button onClick={
increment}>+1</button>
</div>
);
});
export default App;
useRef は ref オブジェクトを返します。返された ref オブジェクトはコンポーネントのライフサイクルを通じて変更されません。
28.8 useImperativeHandle
- ref と forwardRef の組み合わせを確認してみましょう。
- Ref は、forwardRef を通じてサブコンポーネントに転送できます。
- 子コンポーネントは、親コンポーネントで作成された ref を取得し、それを独自の要素の 1 つにバインドします。
- forwardRef アプローチには何も問題はありませんが、子コンポーネントの DOM を親コンポーネントに直接公開します。
- 親コンポーネントに直接さらされることによって引き起こされる問題は、特定の状況が制御できないことです。
- 親コンポーネントは、DOM を取得した後に任意の操作を実行できます。
import React, {
memo, useRef, forwardRef, useImperativeHandle } from "react";
const HelloWorld = memo(
forwardRef((props, ref) => {
const inputRef = useRef();
// 子组件对父组件传入的ref进行处理
useImperativeHandle(ref, () => {
return {
focus() {
console.log("focus");
inputRef.current.focus();
},
setValue(value) {
inputRef.current.value = value;
},
};
});
return <input type="text" ref={
inputRef} />;
})
);
const App = memo(() => {
const titleRef = useRef();
const inputRef = useRef();
function handleDOM() {
// console.log(inputRef.current)
inputRef.current.focus();
// inputRef.current.value = ""
inputRef.current.setValue("哈哈哈");
}
return (
<div>
<h2 ref={
titleRef}>哈哈哈</h2>
<HelloWorld ref={
inputRef} />
<button onClick={
handleDOM}>DOM操作</button>
</div>
);
});
export default App;
- 上記の場合、親コンポーネントがフォーカスを操作できるようにするだけで、任意に操作できるようにする必要はありません。
- 固定操作は useImperativeHandle 経由で公開できます。
- useImperativeHandle のフックを通じて、受信 ref と useImperativeHandle の 2 番目のパラメーターによって返されるオブジェクトがバインドされます。
- したがって、親コンポーネントで inputRef.current を使用すると、返されたオブジェクトが実際に使用されます。
- たとえば、focus 関数を呼び出すと、printHello 関数を呼び出すこともできます。
23.9 useLayoutEffect
- useLayoutEffect は useEffect と非常によく似ていますが、実際には 1 つだけ違いがあります。
- useEffect は、レンダリングされたコンテンツが DOM に更新された後に実行され、DOM の更新をブロックしません。
- useLayoutEffect は、レンダリングされたコンテンツが DOM に更新される前に実行され、DOM の更新がブロックされます。
- 何らかの操作が発生した後に DOM を更新したい場合は、この操作を useLayoutEffect に配置する必要があります。
import React, {
memo, useEffect, useLayoutEffect, useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(100);
useLayoutEffect(() => {
console.log("useLayoutEffect");
if (count === 0) {
setCount(Math.random() + 99);
}
});
console.log("App render");
return (
<div>
<h2>count: {
count}</h2>
<button onClick={
(e) => setCount(0)}>设置为0</button>
</div>
);
});
export default App;
useLayoutEffect の代わりに useEffect を使用することが公式に推奨されています。
23.10 カスタムフック
スクロール位置を取得する
import { useState, useEffect } from "react";
function useScrollPosition() {
const [scrollX, setScrollX] = useState(0);
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
function handleScroll() {
// console.log(window.scrollX, window.scrollY)
setScrollX(window.scrollX);
setScrollY(window.scrollY);
}
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
return [scrollX, scrollY];
}
export default useScrollPosition;
localStorage データストレージ
import {
useEffect, useState } from "react";
function useLocalStorage(key) {
// 1.从localStorage中获取数据, 并且数据数据创建组件的state
const [data, setData] = useState(() => {
const item = localStorage.getItem(key);
if (!item) return "";
return JSON.parse(item);
});
// 2.监听data改变, 一旦发生改变就存储data最新值
useEffect(() => {
localStorage.setItem(key, JSON.stringify(data));
}, [data]);
// 3.将data/setData的操作返回给组件, 让组件可以使用和修改值
return [data, setData];
}
export default useLocalStorage;
23.11 redux フック(useDispatch useSelector)
-
以前の redux 開発では、コンポーネントを redux と組み合わせるために、react-redux で connect を使用しました。
- ただし、このメソッドは高階関数を使用して、返された高次コンポーネントを結合する必要があります。
- そして、mapStateToProps および mapDispatchToProps マッピング関数を記述する必要があります。
-
Redux7.1 からは Hook メソッドが提供され、connect および対応するマッピング関数を記述する必要がなくなりました。
-
useSelector の役割は、状態をコンポーネントにマップすることです。
-
パラメータ 1: 状態を必要なデータにマップします。
-
パラメータ 2: コンポーネントを再レンダリングする必要があるかどうかを決定するために比較を行うことができます。
-
useDispatch は非常に簡単で、ディスパッチ関数を直接取得してコンポーネント内で直接使用するだけです。
-
useStore を通じて現在のストア オブジェクトを取得することもできます。
import React, {
memo } from "react";
import {
useSelector, useDispatch, shallowEqual } from "react-redux";
import {
addNumberAction, changeMessageAction, subNumberAction } from "./store/modules/counter";
// memo高阶组件包裹起来的组件有对应的特点: 只有props发生改变时, 才会重新渲染
const Home = memo((props) => {
// 1.使用useSelector将redux中store的数据映射到组件内
const {
message } = useSelector(
(state) => ({
message: state.counter.message,
}),
shallowEqual
);
const dispatch = useDispatch();
function changeMessageHandle() {
// 2.使用dispatch直接派发action
dispatch(changeMessageAction("你好啊, zimo!"));
}
console.log("Home render");
return (
<div>
<h2>Home: {
message}</h2>
<button onClick={
(e) => changeMessageHandle()}>修改message</button>
</div>
);
});
const App = memo((props) => {
// 1.使用useSelector将redux中store的数据映射到组件内
const {
count } = useSelector(
(state) => ({
count: state.counter.count,
}),
shallowEqual
);
// 2.使用dispatch直接派发action
const dispatch = useDispatch();
function addNumberHandle(num, isAdd = true) {
if (isAdd) {
dispatch(addNumberAction(num));
} else {
dispatch(subNumberAction(num));
}
}
console.log("App render");
return (
<div>
<h2>当前计数: {
count}</h2>
<button onClick={
(e) => addNumberHandle(1)}>+1</button>
<button onClick={
(e) => addNumberHandle(6)}>+6</button>
<button onClick={
(e) => addNumberHandle(6, false)}>-6</button>
<Home />
</div>
);
});
export default App;