赞
踩
作为一个重度 Vue 使用者,在学习使用React时难免有些不适应,甚至有点急躁。
但时事变迁,现在不学 React,找工作真的很难呀。
所以纵使 React 再不好学,也要熟练运用它。
毕竟编程就是这样,有些语言可能不太好理解,不过既然你不是框架开发者,只能适应它。
学 React 的时候,就先把 Vue 给忘掉。
不要急,慢慢来。
如果看不懂,可以跳着看,先把能看懂的吸收掉。
- function Test() {
- return (
- <>
- <p dangerouslySetInnerHTML={{ __html: '<i style="color:red;">123</i>' }} />
- </>
- )
- }
- 复制代码
不像 Vue 有 v-if、v-for 指令,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} />
- </>
- )
- }
- }
- 复制代码

表单的输入显示不受 state 控制。而是取得表单元素的 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,要注意不能直接修改原数据。当需要修改层级很深的对象时,为了方便可以使用 immer 库。(比较流行)
- this.setState({
- userName: '小花',
- userAge: 19
- })
- 复制代码
React 常用的生命周期函数有 componentDidMount、shouldComponentUpdate。 componentDidMount 和 Vue 的 mounted 钩子差不多。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;
- }
- 复制代码
就会出现这样的情况。
将子组件改为 Portal 后,问题立马得到解决。
- 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 是一点也没搞明白,什么玩意儿!但要是理解了 React 函数式组件的渲染逻辑,就不会那么懵逼了。
React 函数式组件不像 class 组件,假设你在函数式组件里定义了一个方法,那么每次重新渲染时都会定义一个新的方法,与之前方法的引用并不相同。
而在 class 组件里定义一个方法,它的引用是不会变的,重新渲染也只是重新执行了 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>
- </>
- );
- }
- 复制代码

这里想想阐述的是,每次 React 改变state,函数式组件都会重新执行一遍,它的子组件也会跟着重新执行一遍。
如果想让它的子组件不会重新执行一遍,可以使用 React.memo 包裹子组件,它会使用 Object.is 来对比前后的 prop 是否相同, 不同就会重新渲染子组件。
在这里,我写了一个名为 Login 的组件(名字不重要,jy),它使用到了 Test 组件。
- 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} />
- </>
- );
- }
- 复制代码

Test 组件如下
- import React from 'react';
-
- function Test(props: any) {
- console.log('Test函数组件执行了一遍');
-
- return <button>Test</button>
- }
-
- export default React.memo(Test)
- 复制代码
现在我只有一个 Login 组件(不要在意名字,jym)。
为什么出现下面这种情况?
第一种情况依赖是个空数组,useCallback 它会将传入的函数进行保存,且它永远不会更新。 再由于闭包,传入的函数里所获取到的 count 值永远都是 0。所以每次点击按钮传入 setCount 的值其实都是1。
第二种情况以为传入了 count 依赖,在 count 变化后,useCallback 里的函数也会重新定义,拿到本次执行 Login 函数时定义的 count 值。 此时的 count 值为最新值。
- 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>
- </>
- );
- }
- 复制代码

使用类的 getter 来实现计算属性,不过实现不了缓存。
- 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>;
- }
- 复制代码
看下一个简单的例子,了解下其用法。
- 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>
- </>
- )
- }
- 复制代码

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。