• React.js |核心概念


    参照react官方文档与书籍程墨的《深入浅出React和Redux》,文章用于笔记整理。

    初始化React项目

    React技术依赖于一个很庞大的技术栈,比如,转译JavaScript代码需要使用Babel,模块打包工具又要使用Webpack……这些技术栈都需要各自的配置文件。现在通过create-react-app工具就可以创建一个具有基本配置的应用。create-react-app是一个通过npm发布的安装包,在确认Node.js和npm安装好之后安装这个工具:

    npm install --global create-react-app
    

    现在创建一个项目:

    create-react-app myapp
    

    它会生成一个名为myapp的目录,这个目录中会自动添加一个应用的框架,避免了大量的手工配置工作。运行项目:

    cd myapp
    npm start
    

    创建完react应用之后,主要关注src目录,其中index.js是入口文件。现在对默认的src目录内容做了一些删减,只保留以下三个文件:

    App.js
    index.css
    index.js
    

    App.js

    import React from 'react';
    function App() {
      return (
        <h1>hello world!</h1>
      );
    }
    export default App;
    

    index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    
    //ES6语法导入模块。ES6语法的js代码会被webpack和babel转译成所有浏览器都支持的ES5语法,而这一切create-react-app已经替我们完成了这些工作。
    import App from './App';
    
    //渲染app组件
    ReactDOM.render(
        <App />,document.getElementById('root')
    );
    

    image.png

    React组件

    React的首要思想是通过组件(Component)来开发应用。所谓组件,指的是能完成某个特定功能的独立的、可重用的代码。把一个大应用分解成若干小的组件,每个组件只关注于某个小范围的特定功能。

    组件类别

    • 函数组件

      function Welcome(props) {
        return <h1>Hello, {props.name}</h1>;
      }
      
    • class组件

      class Welcome extends React.Component {
        //...
        render() {
          return <h1>Hello, {this.props.name}</h1>;
        }
      }
      

    渲染组件

    • React 元素可以是DOM 标签

      const element = <div />;
      
    • 也可以是自定义的组件

      const element = <Welcome name="Sara" />;
      

      当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性以及子组件转换为props对象传递给组件:

      //1.定义组件
      function Welcome(props) {
        return <h1>Hello, {props.name}</h1>;
      }
      
      //2.创建组件并将 {name: 'Sara'} 作为 props对象传入
      const element = <Welcome name="Sara" />;
      
      //3.渲染组件
      ReactDOM.render(
        element,
        document.getElementById('root')
      );
      

    组合组件

    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    //可以多次渲染 Welcome 组件的 App 组件
    function App() {
      return (
        <div>
          <Welcome name="Sara" />
          <Welcome name="Cahal" />
        </div>
      );
    }
    

    组件示例

    ClickCounter.js(在src下创建一个计数组件)

    //从react库中引入了React和Component
    //Component作为所有组件的基类,提供了很多组件共有的功能
    import React,{Component} from 'react';
    
    //使用ES6语法创建一个叫ClickCounter的组件类,继承Component
    class ClickCounter extends Component{
        //构造函数
        constructor(props){
            super(props);
            this.onClickButton=this.onClickButton.bind(this)
            this.state={count:0}
        }
        onClickButton(){
            this.setState({count:this.state.count+1})
        }
        
        //渲染
        render(){
            return(
                <div>
                    <button onClick={this.onClickButton}>+1</button>
                    计数:{this.state.count}
                </div>
            )
        }
    }
    export default ClickCounter
    

    index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import ClickCounter from './ClickCounter';
    
    ReactDOM.render(
        <ClickCounter />,document.getElementById('root')
    );
    

    image.png

    JSX

    所谓JSX,是JavaScript的语法扩展(eXtension),让我们可以在JavaScript中可以编写像HTML一样的代码。下面是一个jsx例子:

    const element = <h1>Hello, world!</h1>;
    

    大括号

    可以在大括号内放置任何有效的js表达式:

    const name = 'Josh Perez';
    const element = <h1>Hello, {name}</h1>;
    const element = <img src={user.avatarUrl}></img>;
    

    自定义组件

    用户定义的组件必须以大写字母开头

    // 错误!React 会认为 <hello /> 是一个 HTML 标签
    function hello() {...}
    // 正确!
    function HelloWorld() {...}
    

    字符串字面量

    //等价
    <MyComponent message={'hello world'} />
    <MyComponent message="hello world" />
    
    //字符串字面量赋值给 prop 时是未转义的
    <MyComponent message={'<3'} />
    <MyComponent message="&lt;3" />
    

    原理

    Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

    <MyButton color="blue" shadowSize={2}>
      Click Me
    </MyButton>
    

    会编译为:

    React.createElement(
      MyButton,
      {color: 'blue', shadowSize: 2},
      'Click Me'
    )
    

    如果没有子节点:

    <div className="sidebar" />
    

    会编译为:

    React.createElement(
      'div',
      {className: 'sidebar'}
    )
    

    由于 JSX 会编译为 React.createElement 调用形式,所以在有 JSX 的地方需要引入React 库:

    //引入
    import React from 'react';
    function WarningButton() {
      return <CustomButton color="red" />;
    }
    

    点语法

    可以使用点语法引用React 组件

    import React from 'react';
    
    const MyComponents = {
      DatePicker: function DatePicker(props) {
        return <div>Imagine a {props.color} datepicker here.</div>;
      }
    }
    
    //点语法引用组件
    function BlueDatePicker() {
      return <MyComponents.DatePicker color="blue" />;
    }
    

    空格与空行

    JSX 会移除行首尾的空格以及空行。与标签相邻的空行均会被删除,文本字符串之间的新行会被压缩为一个空格。

    <div>Hello World</div>
    
    <div>
      Hello World
    </div>
    

    等价

    <div>
      Hello
      World
    </div>
    
    <div>
    
      Hello World
    </div>
    

    JSX子元素

    • 允许由多个 JSX 元素组成

      const element = (
        <div>
          <h1>Hello!</h1>
          <h2>Good to see you here.</h2>
        </div>
      );
      
      <MyContainer>
        <MyFirstComponent />
        <MySecondComponent />
      </MyContainer>
      
    • JS表达式

      <MyComponent>{'foo'}</MyComponent>
      
      function Item(props) {
        return <li>{props.message}</li>;
      }
      
      function TodoList() {
        const todos = ['finish doc', 'submit pr', 'nag dan to review'];
        return (
          <ul>
            {todos.map((message) => <Item key={message} message={message} />)}
          </ul>
        );
      }
      
    • 函数

      <Repeat numTimes={10}>
            {(index) => <div key={index}>This is item {index} in the list</div>}
      </Repeat>
      
    • 布尔类型、Null 以及 Undefined 将会忽略

      //结果相同
      <div />
      <div></div>
      <div>{false}</div>
      <div>{null}</div>
      <div>{undefined}</div>
      <div>{true}</div>
      

    运行时选择类型

    不能将通用表达式作为 React 元素类型。如果想通过通用表达式来动态决定元素类型,需要首先将它赋值给大写字母开头的变量:

    const components = {
      photo: PhotoStory,
      video: VideoStory
    };
    function Story(props) {
      // 错误!
      return <components[props.storyType] story={props.story} />;
    }
    
    function Story(props) {
      // 正确!JSX 类型可以是大写字母开头的变量
      const SpecificStory = components[props.storyType];
      return <SpecificStory story={props.story} />;
    }
    

    包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性 props.children 传递给外层组件。

    <MyComponent>Hello world!</MyComponent>
    

    MyComponent 中的 props.children 是一个简单的未转义字符串 "Hello world!"

    组件数据

    React组件的数据分为两种,prop和state。无论prop或者state的改变,都可能引发组件的重新渲染。prop是组件的对外接口,state是组件的内部状态,对外用prop,内部用state。下面将用一个例子演示:App父组件与ShowScore子组件


    App.js

    import React, { Component } from 'react';
    import ShowScore from './ShowScore'
    class App extends Component{
        render(){
            return(
                <div>
                    <ShowScore name="李华" initValue={80}/>
                    <ShowScore name="小明" initValue={90}/>
                </div>
            )
        }
    }
    export default App
    

    ShowScore.js

    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    
    class ShowScore extends Component{
        constructor(props){
            super(props)
            this.onClickAdd=this.onClickAdd.bind(this)
            this.onClickDec=this.onClickDec.bind(this)
            this.state={
                score:props.initValue||0
            }
        }
        onClickAdd(){this.setState({score:this.state.score+1})}
        onClickDec(){this.setState({score:this.state.score-1})}
    
        render(){ 
            const{name}=this.props
            //等同于:const name =this.props.name
            return(
                <div>
                    <button onClick={this.onClickAdd}>+</button>
                    <button onClick={this.onClickDec}>-</button>
                    <span>{name}的分数为:{this.state.score}</span>
                </div>
            )
        }
    }
    
    ShowScore.propTypes={
        name:PropTypes.string.isRequired,
        score:PropTypes.number
    }
    export default ShowScore
    

    index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    import './index.css';
    ReactDOM.render(
      <App/>,
      document.getElementById('root')
    );
    

    Prop

    prop(property的简写)是从外部传递给组件的数据。每个React组件都是独立存在的模块,组件之外的一切都是外部世界,外部世界就是通过prop来和组件对话的

    给Prop赋值

    父组件App首先引入了子组件ShowScore,并给prop赋值:

    import ShowScore from './ShowScore'
    
    <ShowScore name="李华" initValue={80}/>
    <ShowScore name="小明" initValue={90}/>
    

    读取Prop

    对于子组件ShowScore来说,父组件App就是外部世界。子组件ShowScore接受了外部的prop值。把prop中的initValue赋值给子组件内部状态state中的score:

    constructor(props){
        //...
        this.state={
            score:props.initValue||0
        }
    }
    

    把prop中的name赋值在视图中渲染:

    render(){
        //...
        return(
            <div>
            	...
    			<span>{name}的分数为:{this.state.score}</span>
    		</div>
    	)
    }
    

    propTypes检查

    React通过propTypes来声明自己的接口规范。可以通过增加类的propTypes属性来定义prop规格。在运行时和静态代码检查时根据propTypes判断外部世界是否正确地使用了组件的属性。在下面例子中,如果没有按照规范传prop,会看到控制台会出现警告。

    import PropTypes from 'prop-types';
    
    ShowScore.propTypes={
        name:PropTypes.string.isRequired,
        score:PropTypes.number
    }
    

    Prop只读性

    //纯函数-不会更改入参-多次调用下相同的入参始终返回相同的结果
    function sum(a, b) {
      return a + b;
    }
    
    //非纯函数-更改了入参
    function withdraw(account, amount) {
      account.total -= amount;
    }
    

    Prop默认值

    //Props 默认值为 True
    //等价
    <MyTextBox autocomplete />
    <MyTextBox autocomplete={true} />
    

    属性展开

    function App1() {
      return <Greeting firstName="Ben" lastName="Hector" />;
    }
    
    //如果你已经有了一个 props 对象,你可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象
    function App2() {
      const props = {firstName: 'Ben', lastName: 'Hector'};
      return <Greeting {...props} />;
    }
    

    可以选择只保留当前组件需要接收的 props,并使用展开运算符将其他 props 传递下去

    //将一个函数组件赋值给Button
    const Button = props => {
      //2.解构prop,保留kind
      const { kind, ...other } = props;
      //3.根据保留的kind的值选择className
      const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
      //4.其他的 props通过 ...other 对象传递
      return <button className={className} {...other} />;
    };
    
    const App = () => {
      return (
        <div>
          //1.传prop,有kind和onClick
          <Button kind="primary" onClick={() => console.log("clicked!")}>
            Hello World!
          </Button>
        </div>
      );
    };
    

    props.children

    包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性 props.children 传递给外层组件。下面例子中,MyComponent 的 props.children 是一个简单的未转义字符串 "Hello world!"

    <MyComponent>Hello world!</MyComponent>
    

    State

    state代表组件的内部状态。state记录自身数据变化。由于React组件不能修改传入的prop,所以常常会把prop数据保存在state中。

    初始化State

    constructor(props){
       ...
        this.state={
           //后面的0表示:若没有传prop值,则初始值为0
            score:props.initValue||0
        }
    }
    

    更新State

    //通过this.setState函数更新内部状态state的score值
    onClickAdd(){
        this.setState({score:this.state.score+1})
    }
    

    读取State

    //通过this.state.score可以读取到内部状态state的score值
    render(){
        //...
        return(
            <div>
            	...
    			<span>{name}的分数为:{this.state.score}</span>
    		</div>
    	)
    }
    

    注意

    不要直接修改 State:

    this.state.score = 90;
    

    而是应该:

    this.setState({score: 90});
    

    State 的更新可能是异步的。this.props 和 this.state 可能会异步更新,所以不要依赖他们的值来更新下一个状态。比如:

    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    

    可以让 setState() 接收一个函数而不是一个对象来解决:

    this.setState((state, props) => ({
      counter: state.counter + props.increment
    }));
    

    或者

    this.setState(function(state, props) {
      return {
        counter: state.counter + props.increment
      };
    });
    

    生命周期

    装载过程

    装载过程(Mount):把组件第一次在DOM树中渲染的过程

    1.constructor

    constructor是ES6中每个类的构造函数。要创造一个组件类的实例,都会调用对应的构造函数。注意,无状态的React组件往往就不需要定义构造函数。React组件需要构造函数的目的是:

    • 初始化state
    • 绑定成员函数的this环境

    在ES6语法下,构造函数的this是当前组件实例,它不会与类的成员函数自动绑定。所以,为了方便调用,会在构造函数中将这个实例的特定函数绑定this为当前实例。

    constructor(props){
        //...
        this.onClickAdd=this.onClickAdd.bind(this)
        this.onClickDec=this.onClickDec.bind(this)
    }
    

    2.getInitialState和getDefaultProps

    getInitialState的返回值会用来初始化组件的this.state。但只有用React.createClass方法创造的组件类中才会起作用;getDefaultProps函数的返回值可以作为props的初始值。这个函数只在React.createClass方法创造的组件类才会用到

    const Sample=React.createClass({
        getInitialState:function(){
            return{foo:'bar'}
        },
        getDefaultProps:function(){
            return{sampleProp:0}
        },
    })
    

    用ES6的话,在构造函数中通过给this.state赋值、通过给类属性defaultProps赋值指定props初始值来进行初始化。代码如下:

    class Sample extends React.Component{
        constructor(props){
            super(props);
            this.state={foo:'bar'}
        }
    }
    Sample.defaultProps={
        return{sampleProp:0}
    }
    

    3.render

    render函数是React组件中最重要的函数。render函数并不做实际的渲染动作,它只返回一个JSX描述的结构,最终由React来操作渲染过程。有时候组件的作用不是渲染界面,或者,在某些情况下没有需要渲染的,那就让render函数返回一个null或者false。render函数是一个纯函数,完全根据this.state和this.props来决定返回的结果,而且不要产生任何副作用。一个纯函数不应该引起状态的改变


    4.componentWillMount和componentDidMount

    在装载过程中,componentWillMount会在调用render函数之前被调用,component-DidMount会在调用render函数之后被调用。通常不用定义componentWillMount函数,可以认为这个函数存在的主要目的就是为了和componentDidMount对称。注意,render函数被调用完之后,componentDidMount函数需要等到render返回的东西已经引发了渲染,组件已经被“装载”到了DOM树上之后才执行。

    更新过程

    更新过程(Update):当组件被重新渲染的过程。当props或者state被修改的时候,就会引发组件的更新过程。

    1.componentWillReceiveProps(nextProps)

    只要是父组件的render被调用,在render里面被渲染的子组件就会调用函数。不管父组件传给子组件的props有没有改变,都会触发子组件的componentWillReceiveProps函数。注意,通过this.setState方法触发的更新过程不会调用这个函数,这个函数适合根据新的props值(也就是参数nextProps)来计算出是不是要更新内部状态state。这个函数有必要把传入参数nextProps和this.props作必要对比。nextProps代表的是这一次渲染传入的props值,this.props代表的上一次渲染时的props值,只有两者有变化的时候才有必要调用this.setState更新内部状态。


    2.shouldComponentUpdate(nextProps, nextState)

    shouldComponentUpdate也是React组件中重要的生命周期函数。它决定了一个组件什么时候不需要渲染。

    在更新过程中,React库首先调用shouldComponentUpdate函数,如果这个函数返回true,那就会继续更新过程,接下来调用render函数;反之,如果得到一个false,那就立刻停止更新过程,也就不会引发后续的渲染了。在执行到到函数shouldComponentUpdate的时候,this.state依然是this.setState函数执行之前的值,所以我们要做的实际上就是在nextProps、nextState、this.props和this. state中互相比对。即shouldComponentUpdate的参数是接下来的props和state值,对比this.props和this.state来判断出是返回true还是返回false。


    3.componentWillUpdate和componentDidUpdate

    如果组件的shouldComponentUpdate函数返回true, React接下来就会依次调用对应组件的componentWillUpdate、render和componentDidUpdate函数。

    卸载过程

    卸载过程(Unmount):组件从DOM中删除的过程。

    当React组件要从DOM树上删除掉之前,对应的componentWillUnmount函数会被调用,所以这个函数适合做一些清理性的工作。componentWillUnmount中的工作往往和componentDidMount有关,比如,在componentDidMount中用非React的方法创造了一些DOM元素,如果撒手不管可能会造成内存泄露,那就需要在componentWillUnmount中把这些创造的DOM元素清理掉。

    事件处理

    驼峰式命名

    React 事件的命名采用小驼峰式(camelCase)

    <button onClick={handleClick}>
      ...
    </button>
    

    传入函数

    使用 JSX 语法时需要传入一个函数作为事件处理函数,而不是一个字符串

    //传统html
    <button onclick="activateLasers()">
      Activate Lasers
    </button>
    
    //React
    <button onClick={activateLasers}>
      Activate Lasers
    </button>
    

    阻止默认行为

    不能通过返回 false 的方式阻止默认行为。必须显式的使用 preventDefault 。

    //传统html
    <a href="#" onclick="console.log('...'); return false">
      Click me
    </a>
    
    //react
    function ActionLink() { 
      function handleClick(e) {
        e.preventDefault();
      }
      return (
        <a href="#" onClick={handleClick}>
          Click me
        </a>
      );
    }
    

    声明事件

    用 ES6 class 语法定义一个组件时,通常将事件处理函数声明为 class 中的方法

    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
        this.handleClick = this.handleClick.bind(this);
      }
        
      //在类中定义
      handleClick() {
        this.setState(state => ({isToggleOn: !state.isToggleOn}));
      }
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        );
      }
    

    在 JavaScript 中,class 的方法默认不会绑定 this。这其实与 JavaScript 函数工作原理有关。通常情况下,如果没有在方法后面添加 (),例如 onClick={this.handleClick},就应该为这个方法绑定 this。如果不想使用bind,可以采取下面的方法:

    1.public class fields 语法

    class LoggingButton extends React.Component {
      constructor(props) {...}
      //...
      handleClick = () => {...}
    }
    

    2.在回调中使用箭头函数

    <button onClick={() => this.handleClick()}>
    

    传递参数

    //删除指定行
    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
    

    在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递;而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

    条件渲染

    function UserGreeting() {return <h1>Welcome back!</h1>}
    function GuestGreeting() {return <h1>Please sign up.</h1>}
                              
    function Greeting(props) {
      const isLoggedIn = props.isLoggedIn;
      if (isLoggedIn) {return <UserGreeting />}
      return <GuestGreeting />;
    }
    
    ReactDOM.render(
      <Greeting isLoggedIn={false} />,
      document.getElementById('root')
    );
    

    元素变量

    使用变量来储存元素,可以有条件地渲染组件的一部分

    import React, { Component } from 'react';
    import Greeting from './Greeting'
    
    //按钮切换组件
    function LoginButton(props) {return <button onClick={props.onClick}>Login</button>}
    function LogoutButton(props) {return <button onClick={props.onClick}> Logout</button>}
    
    class LoginControl extends React.Component {
      constructor(props) {
        super(props);
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);
        this.state = {isLoggedIn: false};
      }
      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>
        );
      }
    }
    export default LoginControl
    

    元素符&&

    可以在JSX 中用内联条件渲染——元素符&&。true && expression 总是会返回 expression, 而 false && expression 总是会返回 false,React 会忽略并跳过它。

    function Mailbox(props) {
        const unreadMessages = props.unreadMessages;
        return <div>
            {unreadMessages.length > 0 &&<h2>You have {unreadMessages.length} unread messages.</h2>}
        </div>
    }
    
    const messages = ['快起床!', '快吃饭!', '快写作业!'];
    ReactDOM.render(
      <Mailbox unreadMessages={messages} />,
      document.getElementById('root')
    );
    

    三目运算符

    condition ? true : false

    function IsLogin(props) {
        const isLoggedIn = props.isLoggedIn;
        return (
          <div>The user is {isLoggedIn ? 'currently' : 'not'} logged in.</div>
        );
    }
    
    ReactDOM.render(
      <IsLogin isLoggedIn={false}/>,
      document.getElementById('root')
    );
    

    阻止组件渲染

    让组件直接返回 null

    function WarningBanner(props) {
      if (!props.warn) {
        return null;
      }
      return (
        <div className="warning">
          Warning!
        </div>
      );
    }
    

    列表 & Key

    渲染多个组件

    使用 {} 在 JSX 内构建一个元素集合:

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

    基础列表组件

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

    Key

    key 帮助 React 识别哪些元素改变了。一般情况下,应当给数组中的每一个元素赋予一个确定的标识,否则控制台会出现警告。

    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) =>
      <li key={number.toString()}>
        {number}
      </li>
    );
    

    key 最好是这个元素在列表中唯一的字符串

    const todoItems = todos.map((todo) =>
      <li key={todo.id}>
        {todo.text}
      </li>
    );
    

    当元素没有确定 id 的时候,使用元素索引 index 作为 key(不推荐)

    const todoItems = todos.map((todo, index) =>
      <li key={index}>
        {todo.text}
      </li>
    );
    

    元素的 key 只有放在就近的数组上下文中才有意义

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

    key 在其兄弟节点之间应该是唯一的。但不需要是全局唯一

    在 JSX 中嵌入 map()

    //原始
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <ListItem key={number.toString()} value={number} />
      );
      return <ul>{listItems}</ul>
      );
    }
    
    //JSX
    function NumberList(props) {
      const numbers = props.numbers;
      return (
        <ul>{numbers.map((number) =><ListItem key={number.toString()}value={number}/>)}</ul>
      );
    }
    

    表单

    受控组件

    在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。渲染表单的 React 组件控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”

    class NameForm extends Component {
        constructor(props) {
          super(props);
          this.state = {value: ''};
          this.handleChange=this.handleChange
          this.handleSubmit=this.handleSubmit
        }
        handleChange=(e)=> {this.setState({value: e.target.value})}
        handleSubmit=(e)=> {
          alert('提交的名字: ' + this.state.value);
          e.preventDefault();
        }
        render() {
          return (
            <form onSubmit={this.handleSubmit}>
              <label> 名字:
                <input type="text" value={this.state.value} onChange={this.handleChange} />
              </label>
              <input type="submit" value="提交" />
            </form>
          );
        }
      }
    

    textarea

    在 React 中,<textarea> 使用 value 属性代替。和使用单行 input 的表单非常类似

    <textarea value={this.state.value} onChange={this.handleChange} /> 
    

    select

    React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性

    class FlavorForm extends React.Component {
      constructor(props) {
        super(props);
        //默认选中
        this.state = {value: 'coconut'};
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {this.setState({value: event.target.value});}
      handleSubmit(event) {
        alert('你喜欢的风味是: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              选择你喜欢的风味:
              <select value={this.state.value} onChange={this.handleChange}>
                <option value="grapefruit">葡萄柚</option>
                <option value="lime">酸橙</option>
                <option value="coconut">椰子</option>
                <option value="mango">芒果</option>
              </select>
            </label>
            <input type="submit" value="提交" />
          </form>
        );
      }
    }
    

    可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项:

    <select multiple={true} value={['grapefruit', 'lime']}>
    

    多个input

    class Reservation extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          isGoing: true,
          Guests: 2
        };
        this.handleInputChange = this.handleInputChange.bind(this);
      }
    
      handleInputChange(e) {
        const target = e.target;
        //先获取值
        const value = target.name === 'isGoing' ? target.checked : target.value;
        //获取input的名称
        const name = target.name;
        //这样写的前提:input的value值与name保持一致
        this.setState({
          [name]: value
        });
      }
    
      render() {
        return (
          <form>
            <label>
              参与:
              <input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} />
            </label>
            <br />
            <label>
              来宾人数:
              <input name="Guests" type="number" value={this.state.Guests} onChange={this.handleInputChange} />
            </label>
          </form>
        ); 
      }
    }
    

    空值

    在受控组件指定 value 的 prop 会阻止用户更改输入。但如果你的prop传undefinednull,输入仍可编辑。

    ReactDOM.render(<input value="hi" />, mountNode);
    
    setTimeout(function() {
      ReactDOM.render(<input value={null} />, mountNode);
    }, 1000);
    

    React特点

    • React理念

    React的理念归结为一个公式:UI=render(data)。用户看到的界面(UI),应该是一个函数(在这里叫render)的执行结果,只接受数据(data)作为参数。如此一来,最终的用户界面,在render函数确定的情况下完全取决于输入数据。想要更新用户界面,要做的就是更新data,用户界面自然会做出响应,所以React实践的也是“响应式编程”(Reactive Programming)的思想。

    • Virtual DOM

    React利用Virtual DOM,让每次渲染都只重新渲染最少的DOM元素。

    要了解Virtual DOM,就要先了解DOM, DOM是结构化文本的抽象表达形式,特定于Web环境中,这个结构化文本就是HTML文本。HTML中的每个元素都对应DOM中某个节点,这样,因为HTML元素的逐级包含关系,DOM节点自然就构成了一个树形结构,称为DOM树。

    浏览器为了渲染HTML格式的网页,会先将HTML文本解析以构建DOM树,然后根据DOM树渲染出用户看到的界面,当要改变界面内容的时候,就去改变DOM树上的节点。

    Web前端开发关于性能优化有一个原则:尽量减少DOM操作。虽然DOM操作也只是一些简单的JavaScript语句,但是DOM操作会引起浏览器对网页进行重新布局,重新绘制,这就是一个比JavaScript语句执行慢很多的过程。

    虽然JSX看起来很像是一个模板,但是最终会被Babel解析为一条条创建React组件或者HTML元素的语句,神奇之处在于,React并不是通过这些语句直接构建DOM树,而是首先构建Virtual DOM。

    既然DOM树是对HTML的抽象,那Virtual DOM就是对DOM树的抽象。VirutalDOM不会触及浏览器的部分,只是存在于JavaScript空间的树形结构,每次自上而下渲染React组件时,会对比这一次产生的Virtual DOM和上一次渲染的VirtualDOM,对比就会发现差别,然后修改真正的DOM树时就只需要触及差别中的部分

    以ClickCounter为例,一开始点击计数为0,用户点击按钮让点击计数变成1,这一次重新渲染,React通过Virtual DOM的对比发现其实只是id为clickCount的span元素中内容从0变成了1而已:

    <span id="clickCount">{this.state.count}</span>
    

    React发现这次渲染要做的事情只是更换这个span元素的内容而已,其他DOM元素都不需要触及,于是执行类似下面的语句,就完成了任务:

    document.getElementById("clickCount").innerHTML="1";
    
    • 工作方式的优点

    对比jQuery的方式直观易懂,当项目逐渐变得庞大时,用jQuery写出的代码往往互相纠缠,难以维护:

    image.png

    而React可以避免构建这样复杂的程序结构,无论何种事件,引发的都是React组件的重新渲染,至于如何只修改必要的DOM部分,则完全交给React去操作:

    image.png

    组件设计

    作为软件设计的通则,组件的划分要满足高内聚(High Cohesion)和低耦合(LowCoupling)的原则:

    高内聚指的是把逻辑紧密相关的内容放在一个组件中。传统上,内容由HTML表示,交互行放在JavaScript代码文件中,样式放在CSS文件中定义。一个功能模块却要放在三个不同的文件中,这其实不满足高内聚的原则。React中展示内容的JSX、定义行为的JavaScript代码,甚至定义样式的CSS,都可以放在一个JavaScript文件中。

    低耦合指的是不同组件之间的依赖关系要尽量弱化,也就是每个组件要尽量独立。React组件的对外接口非常规范,方便开发者设计低耦合的系统。

    组件向外传递数据

    组件之间的交流是相互的,子组件某些情况下也需要把数据传递给父组件。

    在上面例子中,父组件App包含两个子组件ShowScore。每个ShowScore都有一个可以动态改变的分数值,现在希望父组件App能够即时显示出这两个子组件当前分数值之和。

    ShowScore.js

    //改装一下函数
    onClickAdd(){this.updateScore(true)}
    onClickDec(){this.updateScore(false)}
    updateScore(isIncrement){
        const previousScore=this.state.score;
        const newScore=isIncrement?previousScore+1:previousScore-1
        //不仅可以改变内部状态
        this.setState({score:newScore})
        //还能通过this.props.onUpdate将状态传递给父组件
        this.props.onUpdate(newScore,previousScore)
    }
    

    App.js

    //添加构造函数。使其可以绑定成员函数的this环境
    constructor(props){
        super(props);
        
        //初始状态数据与初始状态总和
        this.initValue=[10,20]
        const initSum=this.initValue.reduce((a,b)=>a+b,0)
        
        this.onScoreUpdate=this.onScoreUpdate.bind(this)
        this.state={
            sum:initSum
        }
    }
    
    //定义函数
    onScoreUpdate(newScore,previousScore){
        const ScoreChange=newScore-previousScore;
        //让初始总和+/-变化的值
        this.setState({sum:this.state.sum+ScoreChange})
    }
    
    //给子组件传递prop。现在只要子组件ShowScore触发了onUpdate,就会调用父组件的onScoreUpdate
    <ShowScore name="李华" initValue={this.initValue[0]} onUpdate={this.onScoreUpdate}/>
    <ShowScore name="小明" initValue={this.initValue[1]} onUpdate={this.onScoreUpdate}/>
    <div>总分数:{this.state.sum}</div>
    

    image.png

    state和prop的局限

    是时候重新思考一下多个组件之间的数据管理问题了。上面的例子中每个ShowScore组件有自己的状态记录当前分数,而父组件App也有一个状态来存储所有ShowScore分数总和。也就是数据发生了重复。如果数据重复,带来的一个问题就是如何保证重复的数据一致。

    image.png

    另一种思路,就是干脆不要让任何一个React组件储存数据,而是把数据源放在React组件之外形成全局状态,让各个组件保持和全局状态的一致,这样更容易控制:

    image.png

    这就是Flux和Redux中Store的概念。

    所涉及的示例源码可在这里获取(不完全)

  • 相关阅读:
    6、javac命令详解
    5、main方法详解
    4、第一个JAVA程序(Hello World)
    3、eclipse 查看原始类出现The jar file rt.jar has no source attachment解决方法
    2、classpath、path、JAVA_HOME的作用
    1、配置JAVA的环境变量
    Jquery.getJSON的缓存问题的处理方法
    触发器
    JQuery 来获取数据c#中的JSON数据
    从ajax获取的数据无法通过Jquery选择器来调用事件
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13723089.html
Copyright © 2020-2023  润新知