React学习1(基础知识)

138 阅读9分钟

一、首先全局安装create-react-app

在当前目录下面安装: npm install -g create-react-app

npx create-react-app my-app 后出现如图所示问题:

image.png 所以切换node版本

1. nvm ls //查看当前下载的node版本
2. nvm install 16.14.0//安装指定版本的Node.js
3. nvm use 16.14.0 //切换node版本到16.14.0

检查当前的Node.jsnpm版本: image.png

继续执行npx create-react-app my-app命令:

image.png 找到日志:

image.png 找到对应的文件:

image.png

依然报错;

  1. 文件命名从React App更改为ReactApp ;
  2. 删除npm-cache文件,重新安装node版本;
  3. 安装Node.js 版本为16.12.0 npm为8.1.0;
  4. 重新执行 npm install -g create-react-app->npx create-react-app my-app; Ok

image.png

之前一个是文件夹命名问题,一个是安装Node.js版本是LTS,重新安装切换node.js版本即可。

二、概念学习

1. Hello World

首先在html文件中搭好html结构;

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CDN连接</title>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
    <div>CDN 连接使用</div>
    <div id="root"></div>
    
    <script type="text/babel" src="./index.js"></script>
</body>

index.js文件中:

ReactDom.render(
    <h1>hello world!!</h1>,
    document.getElementById('root')
);

因为使用JSX语法,所以引用babel插件

2. JSX简介

JSX需要搭配babel使用,BabelJSX编译成React.createElement调用。 有关JSX具体介绍参考JSX 介绍 - React 中文网 (caibaojian.com.cn)

3. 元素渲染

把一个React元素渲染到root DOM节点,需要把他们传递给ReactDOM.render()方法:

const element = <h1>hello</h1>;
ReactDOM.render(element,document.getElementById('root'));

React元素不可突变,创建元素之后不能修改其子元素或任何属性,更新UI的唯一方法是创建一个新的元素,将其传入ReactDOM.render()方法。

React DOM只更新必须要更新的部分,即只更新有变动的部分; React DOM将元素及其子元素与之前的版本逐一对比,并只对必须更新的DOM进行更新。

4. 组件(Components)和属性(Props)

组件分为函数式组件类组件

//1. 函数式组件
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>
}
//2. ES6定义一个组件
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>
}
class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>
    }
}

React元素既可以表示DOM标签,也可以表示用户自定义的组件:

//1. React元素表示DOM标签
const element = <h1>Hello, {name1}</h1>;
//2. React元素表示定义的组件(函数组件)
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name = 'xll'/>;

React遇到代表用户组件的元素时,将JSX属性以单独对象的形式传递给相应的组件,称其为‘props’对象。 合成组件,组件中可以引用组件,可以把同样的组件抽象到任意层级,一个按钮一个表单,一个对话框,一个屏幕等。

//1. 函数组件Welcome
function Welcome(props) {
    return (<div>
        <h1>Hello, {props.name}</h1>
        <h1>My age is {props.age}</h1>
    </div>);
}
//2. 函数组件App
function App() {
    return (<div>
        <Welcome name = 'xll' age = '23'/>
        <Welcome name = 'dwz' age = '25'/>
</div>);
}

问题: 组件(Components) 和 属性(Props) - React 中文网 (caibaojian.com.cn)

image.png React不能直接渲染对象,应该渲染到具体的属性,如:

image.png

props.user是一个对象,应该渲染props.user.name或者props.user.avatarUrl

5. 状态(State)和生命周期

image.png 实现上图中的秒钟,初始代码如下:

function Clock(props) {
    return (
        <div>
            <h1>Hello world!</h1>
            <h2>It is {props.date.toLocaleTimeString()}</h2>
        </div>
    );
}
function tick() {
    ReactDOM.render(
        <Clock date={new Date()}/>,
        document.getElementById('root')
           );
}
setInterval(tick,1000);

理想情况下,应该只引用一个Clock组件,自动计时并更新。可以通过添加stateClock组件,使用类组件可实现。

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    //在类中添加生命周期方法 
    componentDidMount() {
        //Clock第一次渲染到DOM时,挂载componentDidMount
        //在挂载时,设置时钟
        this.timerID = setInterval(
            () => this.tick(),
            1000
        );
    }
    componentWillUnmount() {
        //Clock产生的DOM销毁时,卸载componentWillUnmount
        //销毁时钟
        clearInterval(this.timerID);
    }
    tick() {
        this.setState({
            date: new Date()
        });
    }

    render() {
        return (
            <div>
                <h1>Hello world!</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}</h2>
            </div>
        );
    }
}

ReactDOM.render(
    <Clock/>,
    document.getElementById('root')
);

this.setState();是固定函数,用来设置state状态

  1. setState() 不能直接修改state状态,只能使用上述方法;
  2. 唯一可以分配this.state的地方是构造函;
  3. state(状态)更新可能是异步的;
  4. state(状态)更新会被合并,当调用setState()时,sate可能包含几个独立的变量,如下:
this.state = {
    posts: [],
    comments: []
};

当调用setState()时,更新posts会自动同步到state(状态中)

state(状态)被称为本地状态或封装状态的原因,不能被拥有设置它的组件以外的组件访问,即谁设置谁有权限访问,但可以组件可以将state(状态)向下传递,作为其子组件的props(属性)。如:

function FormattedDate(props) {
    return <h2>It is {props.date.toLocaleTimeString()}.</h2>
}
<FormattedDate date={this.state.date}/>

React应用中,可以在有状态的组件中使用无状态组件,也可以在无状态组件中使用有状态组件。

6. 处理事件

通过React元素处理事件与在DOM元素上处理事件非常相似。但存在如下区别:

  1. React事件使用驼峰命名。
  2. 通过JSX,传递一个函数作为事件处理程序,而不是一个字符串。

HTML中使用事件:

<button onclick="activateLasers()">
Activate Lasers
</button>

React中使用事件:

<button onclick={activateLasers}>
Activate Lasers
</button>

React中使用事件,必须明确调用preventDefault来阻止默认行为。 使用函数组件:

function ActionClick(e) {
    function handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked.');
    }
    return (
        <a href="#" onClick={handleClick}>
            Click me
        </a>
    );
}
ReactDOM.render(
    <ActionClick/>,
    document.getElementById('button')
)

实现效果: image.png 使用类组件:

class ActionClick extends React.Component {
    handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked.');
    }
    render() {
        return (
            <a href="#" onClick={this.handleClick}>
                Click me
            </a>
        );
    }
}
ReactDOM.render(
    <ActionClick/>,
    document.getElementById('button')
)

使用React时,一般不需要调用addEventListener事件监听器,只需要在元素初始被渲染时,提供一个监听器。实现onoff状态切换,如下:

class Toggle extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
        this.handleClick = this.handleClick.bind(this);//绑定this
    }
    
    handleClick() {
        this.setState(prevSate => ({
            isToggleOn: !prevSate.isToggleOn
        }))
    }

    render() {
        return (
            <button onClick={this.handleClick} style={this.state.isToggleOn ? {color:'red',backgroundColor:'blue'}:{color:'blue',backgroundColor:'red'}}>
                {this.state.isToggleOn ? 'on' : 'off'}
            </button>
        );
    }
}

ReactDOM.render(
    <Toggle/>,
    document.getElementById('root')
);

onAndOff.gif

JavaScript中,类方法默认是没用绑定的,如果未使用this.handleClick = this.handleClick.bind(this);,thisundefined;除了上述显示的绑定this以外,还可以使用箭头函数的this指向属性,箭头函数的this指向定义箭头函数的地方,所以:

handleClick = () => {
        console.log('this is :',this)
    }

实现事件函数的传参:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

需要注意:箭头函数不需要显示绑定this,但事件对象e必须显示传递;而使用bind显示绑定this之后,事件对象e无需显示传递。

7. 条件渲染

React中,创建不同大的组件封装不同的行为,根据需求只渲染其中一些。

function UserGreeting(props) {
    return (
        <h1 style={{ backgroundColor: 'pink' }}>Welcome back!</h1>
    );
}

function GuestGreeting(props) {
    return (
        <h1 style={{ backgroundColor: 'blue' }}>Please sign up!!!</h1>
    );
}

function Greeting(props) {
    const isLoggedIn = props.isLoggedIn;
    if (isLoggedIn) { 
        return (
            <UserGreeting/>
        );
    } else {
        return (
            <GuestGreeting/>
        );
    }
}

ReactDOM.render(
    <Greeting isLoggedIn={true}/>,
    document.getElementById('root')
);

以上内容根据isLoggedIn取值来渲染不同组件。

示例7.1:考虑使用按钮点击控制组件渲染,代码如下:

class ClickButton extends React.Component {
    constructor(props) {
        super(props);
        this.state = { isLoggedIn: true };
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        this.setState(prevState => ({
            isLoggedIn: !prevState.isLoggedIn
        }))
    }

    render() {
        return (
            <div>
                <button onClick={this.handleClick}>
                {this.state.isLoggedIn ? 'true' : 'false' }
                </button>
                <Greeting isLoggedIn={this.state.isLoggedIn}/>
            </div>
        );
    }
}

ReactDOM.render(
    <ClickButton/>,
    document.getElementById('root')
);

render函数里面return只能包含一个根节点!!,上述代码效果如下:

动态渲染组件.gif

示例7.2:状态组件LoginControl,根据条件渲染不同组件LoginButtonLogoutButton

function LoginButton(props) {
    return (
        <button onClick={props.onClick} style={{color:'red',backgroundColor:'blue'}}>
            登录!
        </button>
    );
}

function LogoutButton(props) {
    return (
        <button onClick={props.onClick} style={{color:'purple',backgroundColor:'pink'}}>
            Logout!
        </button>
    );
}

class LoginControl extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isLoggedIn: false};
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);
    }

    handleLoginClick() {
        this.setState({isLoggedIn: true});
    }

    handleLogoutClick() {
        this.setState({isLoggedIn: false});
    }

    render() {
        const isLoggedIn = this.state.isLoggedIn;
        let button;
        if (isLoggedIn) {
            button = <LogoutButton onClick={this.handleLogoutClick}/>
        } else {
            button = <LoginButton onClick={this.handleLoginClick}/>
        };

        return (
            <div>
                <Greeting isLoggedIn={isLoggedIn}/>
                {button}
            </div>
        );
    }
}

ReactDOM.render(
    <LoginControl/>,
    document.getElementById('root')
);

示例1是一个按钮组件,这里使用了两个不同的按钮组件。效果如下:

动态渲染组件.gif

除了上述使用if else有条件的渲染组件以外,开可以使用&&以及三目运算符实现有条件的渲染组件。

示例7.3:&&运算符

function Mailbox(props) {
    const unreadMessages = props.unreadMessages;
    return (
        <div>
            {unreadMessages.length>0 && 
                <h2>
                    You have {unreadMessages.length} unread messages.
                </h2>
            }
        </div>
    )
}

const messages = ['React','JavaScript','Hello','XLL'];
ReactDOM.render(
    <Mailbox unreadMessages={messages}/>,
    document.getElementById('root')
);

示例7.4:三目运算符

render() {
        const isLoggedIn = this.state.isLoggedIn;
        return (
            <div>
                <Greeting isLoggedIn={isLoggedIn} />
                {isLoggedIn ? (
                    <LogoutButton onClick={this.handleLogoutClick}/>
                ): (
                    <LoginButton onClick={this.handleLoginClick}/>
                )}
            </div>
        );
    }

防止组件渲染,可以使用return null,如:

function WarningBanner(props) {
    if (!props.warn) {
        return null;
    }

    return (
        <div style={{ color: 'red' }}>
            Warning!!!
        </div>
    );
}

从组件的render方法返回null不会影响组件生命周期方法的触发。componentWillUpdatecomponentDidUpdate仍然会被调用。

8. 列表&key

创建列表组件NumberList

function NumberList(props) {
    const numbers = props.numbers;
    const listItems = numbers.map((number) =>
        <li key={number.toString()}>{number}</li>
    );
    return (
        <ul>{listItems}</ul>
    );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById('root')
);

注意: 需要给列表的key增加一个独一无二的字符串,通常使用数据中的id来作为元素的key,如果数据中没有id,可以使用元素索引index来作为key值。但是列表项目顺序发生变化时,用索引index来作为key值,会导致性能变差,可能引起组件状态的问题。

为什么key是必须的??

对子节点进行递归,在列表末尾增加新元素,更新开销比较小,比如: image.png 在列表表头增加新原色,更新开销比较大,比如: image.png

React不会保留<li>Duke</li><li>Villanova</li>,会重建每一个元素,会带来性能问题,增加不必要的重建操作。为了解决上述问题,React引入key属性,使用key来匹配原有树上的子元素以及最新树上的子元素。key不需要全局唯一,但是在当前列表中需要保持唯一,也可以使用元素在数组中的下标作为key(元素不进行重新排序时比较合适,如果有顺序修改,diff就会变慢)

key提取组件,如果提取出一个ListItem组件,应该把key保留在数组中的这个<ListItem />元素上,而不是放到ListItem组件中的<li>元素上。

image.png

如果需要使用key属性的值,用其他属性名显式传递这个值。

JSX可以在大括号中嵌入任何表达式,可以内联map()返回的结果:但map()嵌套太多层级最好提取。

 return (
        <div>
            {
                <ul>
                    {props.posts.map((post) =>
                        <Lis key={post.id} title={post.title} />
                    )}
                </ul>
            }
        <div/>
);

9. 表单

受控组件: 使用JavaScript函数可以很方便的处理表单的提交,同时还可以访问用户填写的表单数据。在React中,可变状态(mutable state)通常保存在组件的state属性中,并且只能通过使用setState()来更新。

对于受控组件来说,输入的值始终由Reactstate驱动。

  • input标签
  • textarea标签
  • select标签
1. <input type="text" value={this.state.value} onChange={this.handleChange}/>
2. <textarea value={this.state.value} onChange={this.handleChange} />
3. <select value={this.state.value} onChange={this.handleChange}>

非受控组件: 有时使用受控组件比较麻烦,所以可以用非受控组件代替:Formik

image.png

10. 状态提升

多个组件需要反映相同的变化数据,可以将共享状态提升到最近的共同父组件中。

image.png

上面的两个输入框应该同时影响两个输入框下方的提示信息。第一个输入框输入摄氏温度,第二个输入框输入华式温度,无论输入摄氏温度还是华氏温度,两处提示信息应该同步显示。 如下所示:

image.png

在任意一个框输入数值,都可以控制下列显示信息。

状态提升.gif

父组件:Calclulator

image.png

将需要改变的变量放到Calculator中,

image.png 使用this.setState({scale: 'c', temperature});进行改变。

image.png

11. 组合VS继承

使用组合而非继承来实现组件间的代码重用。

image.png

image.png

有点类似插槽slot的概念,但是React中没有槽的概念限制,可以将任何东西作为props进行传递。

自行约定组件,比如:

function SplitPane(props) {
    return (
        <div className="SplitPane">
            <div className="SplitPane-left" style={{color:'blue'}}>
                {props.left}
            </div>
            <div className="SplitPane-right">
                {props.right}
            </div>
        </div>
    );
}

在上述代码中预留了props.leftprops.right两个位置。

function App() {
    return (
        <SplitPane
            left={
                <Contacts />
            }
            right={
                <Chat/>
            }/>
    );
}

12. React哲学

使用React构建一个应用,通过React构建一个可搜索的产品数据表格来深刻领会React哲学。

假设JSON API返回数据为:

[ {category: "Sporting Goods", price: "49.99", stocked: true, name: "Football"}, {category: "Sporting Goods", price: "9.99", stocked: true, name: "Baseball"}, {category: "Sporting Goods", price: "29.99", stocked: false, name: "Basketball"}, {category: "Electronics", price: "99.99", stocked: true, name: "iPod Touch"}, {category: "Electronics", price: "399.99", stocked: false, name: "iPhone 5"}, {category: "Electronics", price: "199.99", stocked: true, name: "Nexus 7"} ];

第一步:将设计好的UI划分为组件层级

根据单一功能原则判定组件范围,一个组件原则上只能负责一个功能。

第二部:用React创建一个静态版本

确定好组件层级,用已有的数据模型渲染一个不包含交互功能的UI。将渲染UI和添加交互两个过程分开。

当应用比较简单的时候使用自上而下的方式更加方便;对于大型的项目,自下而上的构建,同时为底层组件编写测试。