zoukankan      html  css  js  c++  java
  • 从一个例子中体会React的基本面

    【起初的准备工作】


    npm init
    npm install --save react react-dom
    npm install --save-dev html-webpack-plugin webpack webpack-dev-server babel-core babel-loader babel-preset-react
    
    • react react-dom是有关react
    • html-webpack-plugin:用来把源文件,比如把src/index.html复制到dest/中的index.html中,并引用经webpack捆绑后的js文件
    • webpack:不多说
    • webpack-dev-server:搭建一个本地服务器
    • babel-core, babel-loader用来把jsx转换成js文件
    • babel-preset-react:在babel中设置react

    【文件结构】


    app/
    .....index.html
    .....index.js
    .babelrc
    package.json
    webpack.config.js
    
    • .babelrc:在其中设置react
    • webpack.config.js:webpack的配置文件

    【使用webpack需求与实现】

    • 用webpack把js文件捆绑到根目录下的dist文件夹下的index_bundle.js
    • 复制app/index.html文件,在根目录下的dist文件夹下生成一个index.html文件,并引用index_bundle.js
    • 运行npm run 某某名称,来执行webpack -p命令

    也就是:

    app/
    .....index.html
    .....index.js
    dist/
    .....index.html
    .....index_bundle.js
    .babelrc
    package.json
    webpack.config.js
    

    webpack.config.js, 这个webpack的配置文件中大致包括:接受源文件、放到目标文件夹、使用babel把jsx文件转换成js文件、对源文件进行复制,等等。


    var HtmlWebpackPlugin = require('html-webpack-plugin');
    var HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
        template: __dirname + '/app/index.html',
        filename: 'index.html',
        inject: 'body'
    })
    
    module.exports = {
        entry: [
            './app/index.js'
        ],
        output: {
            path: __dirname + '/dist',
            filename: "index_bundle.js"
        },
        module: {
            loaders: [
                {test: /.js$/, exclude: /node_modules/, loader: "babel-loader"}
            ]
        },
        plugins: [
            HtmlWebpackPluginConfig
        ]
    }
    
    • HtmlWebpackPlugin:用来文件复制,template表示源文件的具体地址, filename表示复制到目标文件夹后的文件名称, inject:'body'表示把经webpack生成的index_bundle.js文件被引用到body中
    • module中的loaders使用babel进行jsx到js文件的转换, test使用正则表达式对需要被转换的文件进行限定,exclude是babel在进行转换时需要排除的源文件夹
    • entry:babel在这里找入口源文件
    • output:经babel转换后的文件保存位置
    • plugins:使用HtmlWebpackPlugin,用来文件复制

    .babelrc文件中,babel需要对react的jsx进行转换,配置如下:


    {
        "presets": [
            "react"
        ]
    }
    

    最后一点,如何运行npm run 某某名称,来执行webpack -p命令呢?


    需要在package.json中配置


    {
      "name": "reactjspracticeswithtyler",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "production": "webpack -p"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
        "react": "^15.1.0",
        "react-dom": "^15.1.0"
      },
      "devDependencies": {
        "babel-core": "^6.9.0",
        "babel-loader": "^6.2.4",
        "babel-preset-react": "^6.5.0",
        "bootstrap": "^3.3.6",
        "html-webpack-plugin": "^2.17.0",
        "webpack": "^1.13.1",
        "webpack-dev-server": "^1.14.1"
      }
    }
    

    以上,在scripts下的配置,意思是说运行npm run 某某名称,实际是执行webpack -p命令。


    【第一个React组件】


    app/index.html


    <!doctype html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Untitled Document</title>
        <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
    </head>
    <body>
    
        <div id="app"></div>
    
    </body>
    </html>
    

    以上,我们即将要把React组件插入到id为app的div中。


    app/index.js,在这里创建组件


    var React = require('react');
    var ReactDOM = require('react-dom');
    
    //1、生产Component
    //这里是jsx的语法
    var HelloWorld = React.createClass({
        render: function(){
            //console.log(this.props);
            return (
                <div>Hello {this.props.name}</div>
            )
        }
    });
    
    //2、渲染出来
    ReactDOM.render(
        <HelloWorld name="darren" anySortData={29}/>,
        document.getElementById('app')
    );
    
    • React.createClass创建组件, render必不可少否则报错,return返回的语法就是jsx语法,将来需要用Babel转换成js文件
    • 组件的名称,像这里的HelloWord,第一个字母一般大写
    • ReactDOM.Render用来把组件渲染到DOM上
    • 组件的表现形式就像html元素,比如这里的<HelloWorld name="darren" anySortData={29}/>,这里,对name和anySortData的赋值,实际上会赋值到组件的this.props.name和this.props.anySortData中

    运行npm run production,因为我们在package.json中的scripts下已经有了设置,相当于运行webpack -p命令,接下来会根据webpack.config.js中的设置,找到app/index.js,使用babel把index.js中的jsx部分转换成js保存到dist/index_bundle.js中;这时候,html-webpack-plugin开始工作了,把app/index.html复制保存到dist/index.html中,并把index_bundle.js中注入到dist/index.html的body中。


    还有一个问题需要解决,当在浏览器中输入localhost:8080的时候,能浏览到网页。怎么做呢?


    需要在package.json中进行设置,在scripts中进行如下设置


    {
      "name": "reactjspracticeswithtyler",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "production": "webpack -p",
        "start":"webpack-dev-server"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
        "react": "^15.1.0",
        "react-dom": "^15.1.0"
      },
      "devDependencies": {
        "babel-core": "^6.9.0",
        "babel-loader": "^6.2.4",
        "babel-preset-react": "^6.5.0",
        "bootstrap": "^3.3.6",
        "html-webpack-plugin": "^2.17.0",
        "webpack": "^1.13.1",
        "webpack-dev-server": "^1.14.1"
      }
    }
    

    以上,在scripts下添加了"start":"webpack-dev-server"部分。


    运行npm run start命令,在浏览器中输入localhost:8080就可以看到内容。


    【组件嵌套】


    var USER_DATA = {
        name: 'darren',
        username: 'DarrenActive',
        image: 'https://avatars0.githubusercontent.com/u/2933430?v=38s=460'
    };
    
    
    var React = require('react');
    var ReactDOM = require('react-dom');
    
    //被嵌套组件
    var ProfilePic = React.createClass({
        render: function(){
            return <img src={this.props.imageUrl} style={{height: 100,  100}} />
        }
    })
    
    //被嵌套组件
    var ProfileLink = React.createClass({
        render: function(){
            return (
                <div>
                    <a href={'https://www.github.com/' + this.props.username}>
                        {this.props.username}
                    </a>
                </div>
            )
        }
    });
    
    //被嵌套组件
    var ProfileName = React.createClass({
        render: function(){
            return (
                <div>{this.props.name}</div>
            )
        }
    })
    
    //嵌套组件
    var Avatar = React.createClass({
        render: function(){
            return (
                <div>
                    <ProfilePic imageUrl={this.props.user.image} />
                    <ProfileName name={this.props.user.name} />
                    <ProfileLink username={this.props.user.username} />
                </div>
            )
        }
    })
    
    ReactDOM.render(
        <Avatar user={USER_DATA} />,
        document.getElementById('app')
    );
    

    嵌套组件和被嵌套组件的关系就像河流的上下游,这里的嵌套组件Avatar在河流的上游,这里的被嵌套组件ProfileName在河流的下游,Avatar组件通过user来获取外界的赋值,user接受到值后往下游的ProfileName流动,user中的值再赋值给ProfileName的name,依次类推。


    所以,React的数据流动是单向的,由外向内的流动。


    【组件元素嵌套】


    组件嵌套和组件元素嵌套不一样。组件嵌套大致是:

    var Room = React.createClass({
    	render: function(){
    		return (
    			<Table />
    			<Chair />
    			<Clock />
    		)
    	}
    });
    

    组件元素嵌套大致是:

    var Room = React.createClass({
    	render: function(){
    		return (
    			<Table />
    			<Chair />
    			<Clock>
    				<Time />
    				<Period />
    			</Clock>
    		)
    	}
    });
    

    也就是说,组件元素嵌套中的被嵌套组件,这里的<Time /><Period />不是被放在一个外层的组件中,而是放在了一个组件元素<Clock></Clock>。Clock组件的显示依赖于<Time /><Period />。那么,在定义Clock组件的时候,如何把<Time /><Period />显示出来呢?


    像这种需要显示组件元素内的被嵌套组件,就需要this.props.children的帮忙。


    像在本项目的Avatar组件的写法还是不变:

    var Avatar = React.createClass({
        render: function(){
            return (
                <div>
                    <ProfilePic imageUrl={this.props.user.image} />
                    <ProfileName name={this.props.user.name} />
                    <ProfileLink username={this.props.user.username} />
                </div>
            )
        }
    })
    

    ProfileLink组件,现在想把它作为另外一个组件元素内的被嵌套组件,这样定义ProfileLink组件:

    var ProfileLink = React.createClass({
        render: function(){
            return (
                <div>
                    <Link href={'https://www.github.com/' + this.props.username}>
                        {this.props.username}
                    </Link>
                </div>
            )
        }
    });
    

    以上,{this.props.username}拿到的值是通过ProfileLink组件组件获取到的,如何传递给Link组件呢?


    var Link = React.createClass({
        
        changeURL: function(){
            window.location.replace(this.props.href);
        },
        
        render: function(){
            return (
                <span 
                    style={{color: 'blue', cursor: 'pointer'}}
                    onClick={this.changeURL}>
                    {this.props.children}
                </span>
            )
        }
    });
    

    可见,在Link组件中,通过this.props.children获取到ProfileLink组件这个组件元素内的组件。值得注意的是:this.props.children获取到的是包含在组件元素内的所有被嵌套组件。


    【路由】


    React的路由连接了component和url。


    npm install --save react-router@2.0.0-rc5


    在app文件夹下创建config文件夹,并创建routes.js文件;在app文件夹下创建Home.js和Main.js,现在文件结构变为:


    app/
    .....config/
    ..........routes.js
    .....components/
    ..........Home.js
    ..........Main.js
    .....index.html
    .....index.js
    .babelrc
    package.json
    webpack.config.js
    

    在Home.js中创建一个名称为Home的组件


    var React = require('react');
    
    var Home = React.createClass({
        render: function(){
            return (
                <div>Hello From Home!</div>
            )
        }
    });
    
    module.exports = Home;
    

    在Main.js中创建一个名称为Main的组件


    var React = require('react');
    
    var Main = React.createClass({
        render: function(){
            return (
                <div>
                    Hello From Main!
                    {this.props.children}
                </div>
            )
        }
    });
    
    module.exports = Main;
    

    配置config/routes.js中的路由,其实就是配对url和组件的映射关系。


    var React = require('react');
    var ReactRouter = require('react-router');
    var Router = ReactRouter.Router;
    var Route = ReactRouter.Route;
    var IndexRouter = ReactRouter.IndexRoute;
    
    var Main = require('../components/Main');
    var Home = require('../components/Home');
    
    var routes = (
        <Router>
            <Route path='/' component={Main}>
                <Route path='/home' component={Home} />
            </Route>
        </Router>
    );
    
    module.exports = routes;
    

    以上,路由从本质上说也是组件。<Route path='/' component={Main}>...</Route>依赖于<Route path='/home' component={Home} />,当url为/的时候,只会显示Main组件内容,当url为/home的时候会同时显示Main组件和Home组件的内容。


    在app/index.js中,把路由这个特殊的组件加载起来。


    var React = require('react');
    var ReactDOM = require('react-dom');
    var routes = require('./config/routes');
    
    ReactDOM.render(
        routes,
        document.getElementById('app')
    );
    

    npm run start


    在浏览器中输入:localhost:8080


    url变成:http://localhost:8080/#/?_k=dhjswq
    内容为:Hello From Main!


    因为,当url为/的时候,路由设置显示Main组件,虽然在Main组件定义的时候有{this.props.children},但因为Main组件元素嵌套的Home组件没有显示,所有只显示Main组件的内容。


    在浏览器中输入:http://localhost:8080/#/home?_k=dhjswq


    内容:
    Hello From Main!
    Hello From Home!


    也很好理解,因为Home组件是被嵌套在Main这个组件元素中的,当url为Home路由的时候,不仅把Home组件显示了出来,还把Main组件显示了出来。


    如果想始终都显示Home这个组件呢?


    需要在app/config/routes.js按如下配置


    var React = require('react');
    var ReactRouter = require('react-router');
    var Router = ReactRouter.Router;
    var Route = ReactRouter.Route;
    var hashHistory = ReactRouter.hashHistory;
    var IndexRouter = ReactRouter.IndexRoute;
    
    var Main = require('../components/Main');
    var Home = require('../components/Home');
    
    var routes = (
        <Router history={hashHistory}>
            <Route path='/' component={Main}>
                <IndexRouter  component={Home} />
            </Route>
        </Router>
    );
    
    module.exports = routes;
    

    以上,IndexRouter表示始终都显示的路由。


    【无状态函数式声明组件】


    现在,已经习惯了按这样的方式声明组件:

    var HelloWorld = React.createClass({
    	render: function(){
    		return (
    			<div>Hello {this.props.name}</div>
    		)
    	}
    });
    
    ReactDOM.render(<HelloWorld name='darren' />, document.getElementById('app'));
    

    除了上面的声明方式,还有一种"无状态函数式声明方式"。当React.createClass中只有render方法的时候就可以按如下方式来替代。

    function HelloWorld(props){
    	return (
    		<div>Hello {props.name}</div>
    	)
    }
    
    ReactDOM.render(<HelloWorld name='darren' />, document.getElementById('app'));
    

    **
    【组件中的变量类型约定】**


    定义组件的时候经常用到变量,这些变量的类型可以约定吗?
    --答案是可以的,使用propTypes


    var React = require('react');
    var PropTypes = React.PropTypes;
    var Icon = React.createClass({
    	propTypes: {
    		name: PropTypes.string.isRequired,
    		size: PropTypes.number.isRequired,
    		color: PropTypes.string.isRequired,
    		style: PropTypes.object
    	},
    	render: ...
    });
    

    【生命周期事件】


    所有的组件在生命周期内有一些共同的事件。有些事件在组件与DOM的绑定或解除绑定的时候发生,有些事件在组件接受数据的时候发生。


    给组件设置一些默认属性:


    var Loading = React.createClass({
    	getDefaultProps: function(){
    		return {
    			text: 'Loading'
    		}
    	},
    	render: function(){
    		...
    	}
    });
    

    以上,通过getDefaultProps为组件设置了一些默认属性和其对应的值。


    设置组件的初始状态:


    var Login = React.createClass({
    	getInitialState: function(){
    		return {
    			email: '',
    			password:''
    		}
    	},
    	render: function(){
    		...
    	}
    });
    

    以上,通过getInitialState设置组件的初始状态。


    组件绑定到DOM时触发的事件


    比如,当组件绑定到DOM时从远程获取一些数据:

    var FriendsList = React.createClass({
    	componentDidMount: functioin(){
    		return Axios.get(this.props.url).then(this.props.callback);
    	},
    	render: function(){
    		...
    	}
    });
    

    以上,通过componentDidMount在组件绑定到DOM上后发生事件。


    比如,当组件绑定到DOM时设置监听:

    var FriendsList = React.createClass({
    	componenetDidMount: function(){
    		ref.on('value', function(snapshot){
    			this.setState({
    				friends: snapshot.val()
    			});
    		})
    	},
    	render:...
    });
    

    组件与DOM解除绑定时触发的事件


    var FriendList = React.createClass({
    	componentWillUnmount: function(){
    		ref.off();
    	},
    	render:...
    });
    

    以上,通过componentWillUnmount来触发当组件与DOM解除绑定时的事件。


    当组件接受到新的属性值时触发的事件:componentWillReceiveProps


    决定组件是否需要渲染的事件:shouldComponentUpdate


    所有的事件大致如下图:



    【this关键字】


    隐式绑定


    var me = {
    	name: 'Darren',
    	age:25,
    	sayName: function(){
    		console.log(this.name);
    	}
    };
    
    me.sayName();
    

    以上,当调用sayName的时候,隐式用到了this,这里的this指的是点左边的me.


    来看一个在嵌套函数中使用this关键字的例子。
    var sayNameMixin = function(obj){
    	obj.sayName = function(){
    		console.log(this.name);
    	};
    }
    
    var me = {
    	name: 'Darren',
    	age: 25
    };
    
    var you = {
    	name: 'Joey',
    	age: 21
    };
    
    sayNameMixin(me);
    sayNameMixin(you);
    
    me.sayName();//Darren
    you.sayName();//Joey
    

    以上,sayNameMixin接受me这个参数,然后在其内部给me定义了一个sayName方法,这里的this也是值me。


    再来看用构造函数创建对象,使用this关键字的例子。

    var Person = function(name, age){
    	return {
    		name: name,
    		age: age,
    		sayName: function(){
    			console.log(this.name);
    		}
    	}
    };
    
    var jim = Person('Jim', 42);
    jim.sayName();
    

    以上,调用sayName方法的时候隐式用到了this,这里的this还是指的是点左边的jim。


    所以,这里关于隐式绑定的的总结是:当隐式调用this的时候,this通常指的是点左侧的那个变量。这里的this在定义的时候就很明确。


    显式绑定


    1、使用call方法显式指定this关键字。
    //在定义的时候this指的谁并不明确
    var sayName = function(){
    	console.log('My name is ' + this.name);
    };
    
    var stacy = {
    	name: 'Stacy',
    	age: 34
    };
    
    sayName.call(stacy);
    

    以上,在定义sayName方法的时候并没有明确定义this指的谁。使用call方法的时候把stacy传值给了this关键字。


    call方法不仅可以指向this,还可以传递参数。

    var sayName = function(lang1, lang2, lang3){
    	console.log('My name is ' + this.name + ' and I know ' + lang1 + ', ' + lang2 + ', and ' + lang3);
    };
    
    var stacy = {
    	name: 'Stacey',
    	age:28
    };
    
    var languages = ['JavaScript', 'Ruby', 'Pythos'];
    
    sayName.call(stacey, languages[0], languages[1], languages[2]);
    

    以上,通过调用call方法不仅指定了this关键字,还传递了sayName方法所需的参数。


    2、使用apply方法显式绑定this关键字

    var sayName = function(lang1, lang2, lang3){
    	console.log('My name is ' + this.name + ' and I know ' + lang1 + ', ' + lang2 + ', and ' + lang3);
    };
    
    var stacy = {
    	name: 'Stacey',
    	age:28
    };
    
    var languages = ['JavaScript', 'Ruby', 'Pythos'];
    
    sayName.apply(stacey, languages);
    

    以上,apply和call的区别可见一斑,apply接受的实参数组。


    3、使用bind方法显式绑定this关键字

    var sayName = function(lang1, lang2, lang3){
    	console.log('My name is ' + this.name + ' and I know ' + lang1 + ', ' + lang2 + ', and ' + lang3);
    };
    
    var stacy = {
    	name: 'Stacey',
    	age:28
    };
    
    var languages = ['JavaScript', 'Ruby', 'Pythos'];
    
    var newFn = sayName.bind(stacey, languages[0], languages[1], languages[2]);
    
    newFn();
    

    以上,使用bind方法可以产生一个新的函数,再调用该函数。


    New Binding


    var Animal = function(color, name, type){
    	//this = {}
    	this.color = color;
    	this.name = name;
    	this.type = type;
    }
    
    var zebra = new Animal('black and white', 'Zorro', 'Zebra');
    

    以上,当定义Animal这个函数的时候,此时的this指的是一个空对象,当实例化Animal的时候this就有值了。


    Window Binding


    var sayAge = function(){
    	console.log(this.age);
    }
    
    var me = {
    	age: 25
    };
    
    sayAge(); //undefined
    window.age = 35;
    sayAge(); //35
    

    可见,当没有给this显式绑定的时候,this指的是window。


    接下来,会从一个例子来体会React的方方面面......

  • 相关阅读:
    MongoDB 生态 – 可视化管理工具
    SOLR对多个(关联)表创建索引
    sublime 插件zen coding
    12/12工作笔记
    阅读《深入理解JavaScript定时机制》
    继续谈论XSS
    支付宝快捷支付模型
    谈论XSS
    生产者消费者模型
    如何根据iframe内嵌页面调整iframe高宽续篇
  • 原文地址:https://www.cnblogs.com/darrenji/p/5542230.html
Copyright © 2011-2022 走看看