序文
Vue のヘビー ユーザーである私は、React の使い方を学ぶときにどうしても少し不快になり、さらには少し焦ります。
しかし、現状は変わってきており、今 React を学ばないと仕事を見つけるのは非常に困難です。
したがって、たとえ React を学ぶのが難しくても、それを使いこなすには習熟する必要があります。
結局のところ、プログラミングとはこういうもので、言語によっては理解しにくい場合もありますが、フレームワーク開発者ではないので、それに適応するしかありません。
イデオロギー
React を学習するときは、まず Vue のことを忘れてください。
焦らず、ゆっくり時間をかけてください。
文章
理解できない場合は読み飛ばして、理解できるところから吸収してください。
HTML文字列をレンダリングする
function Test() {
return (
<>
<p dangerouslySetInnerHTML={
{ __html: '<i style="color:red;">123</i>' }} />
</>
)
}
复制代码
条件付きレンダリング、リストレンダリング
v-if および v-for 命令を持つ Vue とは異なり、React は js を直接使用します。
React は配列を自動的に展開してレンダリングします。
export function Login(props: any) {
const [show, setShow] = useState(false)
const [list, setList] = useState(['小梅', '小军', '小强'])
return (
<>
{show ? '显示的文本' : '隐藏的文本'}
<br />
{list}
</>
);
}
复制代码
イベント
伝達関数。以下の例では、父组件
ボタンをクリックした場合とTest组件
ボタンをクリックした場合の両方が出力されます点我了
。
パラメータを渡す必要がある場合は、アロー関数を使用してください。。
これは親コンポーネントです。
export function Login(props: any) {
function handleClick() {
console.log('点我了');
}
return (
<>
<button onClick={handleClick}>父组件</button>
<Test xx={handleClick} />
</>
);
}
复制代码
これは子コンポーネントです。
export function Test(props: any) {
return <button onClick={props.xx}>Test组件</button>
}
复制代码
制御されたコンポーネント
state を使用して、フォームの入力表示を制御します。
class MyComponent extends React.Component{
state = {
inputVal: 'input'
}
handleInput = e => {
this.setState({
inputVal: e.target.value
})
}
render() {
return (
<>
<p>{this.state.inputVal}</p>
<input type="text" value={this.state.inputVal} onInput={this.handleInput} />
</>
)
}
}
复制代码
制御されていないコンポーネント
フォームの入力表示は状態によって制御されません。代わりに、フォーム要素の DOM を取得して、その値を取得または設定します。
import { useRef } from "react";
export function Login(props: any) {
const nameInput = useRef<HTMLInputElement>(null)
function handleClick() {
console.log(nameInput.current?.value);
}
return (
<>
姓名:
<input ref={nameInput} type="text" />
<br />
<button onClick={handleClick}>提交</button>
</>
);
}
复制代码
親子コンポーネントの通信
親は prop を通じて子にデータを渡します。子が親にデータを渡すとき、子は関数も渡し、子はその関数を呼び出して親のデータを渡して変更します。
親コンポーネント
// 父组件
class MyComponent extends React.Component{
state = {
count: 99
}
render() {
return (
<div>
<ChildComponent count={this.state.count} changeCount={this.changeCount} />
</div>
)
}
// 接收子组件传递的count来修改自身的count
changeCount = count => {
this.setState({
count
})
}
}
复制代码
サブアセンブリ
// 子组件
class ChildComponent extends React.Component {
render() {
return <div>
{this.props.count}
<button onClick={this.addCount}>addCount</button>
</div>
}
addCount = () => {
// 获取父组件传递的props
const { changeCount, count } = this.props
// 调用父组件的changeCount方法
changeCount(count + 1)
}
}
复制代码
スロットを実装する
React は Vue のようなデフォルト スロット、名前付きスロット、スコープ スロットを実装しており、実際にはすべてを渡すことができるという React の prop 機能を利用しています。
デフォルトのスロット、this.prop.children を使用
export class Login extends React.Component {
render() {
return (
<>
<Child>我是光</Child>
</>
)
}
}
function Child(props: any) {
return (
<div>
<div>我显示父组件传进来的内容:</div>
<br />
{props.children}
</div>
)
}
复制代码
名前付きスロット。親コンポーネントは必ずしも関数を渡す必要はなく、ラベルを直接レンダリングして、子コンポーネントがそれを受け取ることもできます。
export class Login extends React.Component {
render() {
return (
<>
<Child renderTitle={
<div>这。。。</div>
}>我是光</Child>
</>
)
}
}
function Child(props: any) {
return (
<div>
{props.renderTitle ? props.renderTitle : <div>默认插槽</div>}
</div>
)
}
复制代码
スコープスロット。子コンポーネントは関数パラメータを使用して、親コンポーネントにデータを取り込むことができます。
render() {
return (
<>
<Child renderItem={(item: string, index: number) => {
return <li>{index+1}号,{item}</li>
}}></Child>
</>
)
}
}
function Child(props: any) {
const arr = [
'姚明',
'科比'
]
return (
<div>
{arr.map((item, index) => {
return props.renderItem ? props.renderItem(item, index) : <div>空</div>
})}
</div>
)
}
复制代码
setState
setState を使用する場合は、元のデータを直接変更しないように注意してください。深いレベルのオブジェクトを変更する必要がある場合は、便利なようにimmerライブラリを使用できます 。(より人気のあるもの)
this.setState({
userName: '小花',
userAge: 19
})
复制代码
ライフサイクル
React で一般的に使用されるライフサイクル関数には、componentDidMount と shouldComponentUpdate があります。ComponentDidMount は、Vue のマウントされたフックに似ています。shouldComponentUpdate を使用すると、ユーザーはコンポーネントをレンダリングする必要があるかどうかを決定できます。
class MyComponent extends React.Component{
constructor(props) {
super(props)
}
shouldComponentUpdate (nextProps, nextState, nextContext) {
console.log(nextState.count, this.state.count)
if (nextState.count !== this.state.count) {
return true // 允许渲染,会往下执行render
}
return false // 不渲染,不执行render
}
render() {
return ...
}
}
复制代码
反応ポータル
親要素が overflow: hidden を設定すると、子要素は親要素の境界を越えて表示されなくなります。これはポップアップ コンポーネントにはあまり適していません。
たとえば、親コンポーネントは次のようになります。
export function Login() {
const [show, setShow] = useState(false)
function handleClick() {
setShow(!show)
}
return (
<div className='father'>
<Model show={show} />
<button onClick={handleClick}>打开弹框</button>
</div>
);
}
复制代码
.father {
overflow: hidden;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 250px;
height: 250px;
border: 1px solid #000;
}
复制代码
次に、サブコンポーネント:
function Test(props: any) {
return (
<>
{
props.show ?
<div className='model'>
你好,我是弹框。
</div> :
<div>啥也不是</div>
}
</>
)
}
复制代码
.model {
position: fixed;
left: 50%;
top: 50%;
/* transform: translate(-50%, -50%); */
background-color: red;
color: #fff;
width: 250px;
height: 250px;
border: 1px solid #000;
}
复制代码
こうなるでしょう。
サブコンポーネントをポータルに変更すると、問題はすぐに解決されました。
import React from 'react';
import ReactDOM from "react-dom";
import './test.css'
function Test(props: any) {
return (
<>
{
props.show ?
ReactDOM.createPortal(<div className='model'>你好,我是弹框。</div>, document.body)
:
<div>啥也不是</div>
}
</>
)
}
export default React.memo(Test)
复制代码
反応コンテキスト
VueのProvide/Injectと同じ機能ですが、書くことが少し増えます。
import { createContext,useContext } from "react";
const ThemeContext = createContext('');
export function Login() {
return (
<ThemeContext.Provider value='喔喔喔'>
<div className='father'>
<Test />
</div>
</ThemeContext.Provider>
);
}
function Test(props: any) {
const theme = useContext(ThemeContext);
return (
<>
<div>{theme}</div>
</>
)
}
复制代码
非同期コンポーネント
使用状況を記録するだけです。
import React from "react";
export function Login() {
return (
<div className='father'>
<React.Suspense fallback={<div>loading...</div>}>
<Lazyy />
</React.Suspense>
</div>
);
}
const Lazyy = React.lazy(() => new Promise((resolve) => {
setTimeout(() => {
const a = import('../../components/test')
// @ts-ignore
resolve(a)
}, 1000);
}))
复制代码
useEffect
正直、この useEffect について当時は全く理解できませんでした。ただし、React の機能コンポーネントのレンダリング ロジックを理解していれば、それほど混乱することはありません。
React の機能コンポーネントはクラスコンポーネントとは異なり、機能コンポーネント内でメソッドを定義すると、再レンダリングされるたびに新しいメソッドが定義されるため、以前のメソッドの参照とは異なります。
メソッドがクラス コンポーネントで定義されている場合、その参照は変更されず、再レンダリングでは render メソッドが再実行されるだけです。
React を学習していて useEffect について混乱している場合は、Dan が書いたこの記事を必ず読んでください。useEffect 完全ガイド
import { useState } from "react";
// let a = 1
export function Login(props: any) {
const [count, setCount] = useState(0);
function say () {
let a = 1
console.log('Hello Vue and React, are you ok ?', a ++);
}
say()
return (
<>
<button onClick={() => setCount(count + 1)}>加1,count:{count}</button>
</>
);
}
复制代码
useState、useMemo、useCallback、React.memo
ここで説明したいのは、React が状態を変更するたびに、機能コンポーネントが再実行され、そのサブコンポーネントも再実行されるということです。
サブコンポーネントを再実行したくない場合は、React.memo を使用してサブコンポーネントをラップします。Object.is を使用して、前後の props が同じかどうかを比較します。異なる場合は、サブコンポーネント再レンダリングされます。
ここでは、Test コンポーネントを使用する Login (名前は重要ではありません、jy) というコンポーネントを作成しました。
import { useCallback, useMemo, useState } from "react";
import Test from "components/test";
export function Login(props: any) {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
// Test 组件执行一遍
const data = useMemo(() => ({
lalala: count
}), [])
// Test 组件首次会执行一遍,之后每次 count 加1,Test 组件都会执行一遍
// const data = useMemo(() => ({
// lalala: count
// }), [count])
// Test 组件首次会执行一遍,在 count 为3或4的时候会执行一遍
// const data = useMemo(() => ({
// lalala: count
// }), [count === 3])
return (
<>
<button onClick={() => handleClick()}>加1,count:{count}</button>
<Test suibian={data} />
</>
);
}
复制代码
テストコンポーネントは次のとおりです
import React from 'react';
function Test(props: any) {
console.log('Test函数组件执行了一遍');
return <button>Test</button>
}
export default React.memo(Test)
复制代码
useコールバック
現時点では、ログイン コンポーネントのみがあります (名前は気にしないでください、jym)。
なぜ次のような状況が起こるのでしょうか?
最初のケースでは、依存関係は空の配列であり、渡された関数は useCallback によって保存され、更新されることはありません。また、クロージャーがあるため、渡された関数で取得されるカウント値は常に 0 になります。したがって、ボタンがクリックされるたびに setCount に渡される値は、実際には 1 です。
2 番目のケースでは、カウントの依存関係が渡されます。カウントが変更された後、useCallback 内の関数も再定義され、今回の Login 関数の実行時に定義されたカウント値が取得されます。このときのカウント値が最新の値となる。
import { useCallback, useState } from "react";
export function Login(props: any) {
const [count, setCount] = useState(0);
// 点击按钮后,count 加1,之后永远都为1了。
const handleClick = useCallback(() => {
setCount(count + 1);
}, []);
// 每次点击按钮,count 都会加1
// const handleClick = useCallback(() => {
// setCount(count + 1);
// }, [count]);
return (
<>
<button onClick={() => handleClick()}>加1,count:{count}</button>
</>
);
}
复制代码
React は計算されたプロパティを実装します
クラスのゲッターを使用して計算されたプロパティを実装しますが、キャッシュは実装できません。
class Example extends Component {
state = {
firstName: '',
lastName: '',
};
// 通过getter而不是函数形式,减少变量
get fullName() {
const { firstName, lastName } = this.state;
return `${firstName} ${lastName}`;
}
render() {
return <Fragment>{this.fullName}</Fragment>;
}
}
复制代码
キャッシュを実装したい場合は、memoize-one ライブラリを使用できます。これは本質的に、前後に渡されたパラメータを比較する高階関数です。前回渡されたパラメータと同じ場合、最後にキャッシュされた結果が返されます。それ以外の場合、戻り値は新しい入力パラメータに基づいて再計算されます。
import memoize from 'memoize-one';
import React, { Fragment, Component } from 'react';
class Example extends Component {
state = {
firstName: '',
lastName: '',
};
// 如果和上次参数一样,`memoize-one` 会重复使用上一次的值。
getFullName = memoize((firstName, lastName) => `${firstName} ${lastName}`);
get fullName() {
return this.getFullName(this.state.firstName, this.state.lastName);
}
render() {
return <Fragment>{this.fullName}</Fragment>;
}
}
复制代码
React Hooks を使用して計算プロパティを実装するにはどうすればよいですか? useMemo フックを使用します。
import React, { useState, useMemo } from 'react';
function Example(props) {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// 使用 useMemo 函数缓存计算过程
const renderFullName = useMemo(() => `${firstName} ${lastName}`, [
firstName,
lastName,
]);
return <div>{renderFullName}</div>;
}
复制代码
useReducer
その使用法を理解するために簡単な例を見てください。
import React, { useReducer } from "react";
const initState = {
userName: '匿名',
}
const reducer = (state: any, action: any) => {
switch (action.type) {
case 'add':
return {
...state,
userName: state.userName + '+'
}
case 'del':
return {
...state,
userName: state.userName + '-'
}
default:
return {
...state,
userName: state.userName + '*'
}
}
}
export function Login() {
const [state, dispatch] = useReducer(reducer, initState)
function handleClick() {
dispatch({
type: 'add'
})
}
return (
<>
hello, {state.userName}
<button onClick={handleClick}>按钮</button>
</>
)
}
复制代码
終わり
この記事では特に何も説明しませんが、コードの例とデモを示します。
React に触れたことのない初心者にとっては、理解するのが少し難しいかもしれません。
しかし、理解できないことに遭遇したときは、ブラウザの別のウィンドウを開いて検索し、戻って自分が書いた内容を読むこともできます。
最後に、夢の実現に向けて皆さんと一緒に頑張っていきたいと思います。