GithubHelp home page GithubHelp logo

wscats / react-tutorial Goto Github PK

View Code? Open in Web Editor NEW
421.0 18.0 65.0 6.8 MB

:tiger2:Some of the react tutorial - 《React学习笔记》

JavaScript 98.06% HTML 1.68% CSS 0.08% SCSS 0.14% Less 0.03%
react-tutorial react redux react-router tutorial

react-tutorial's Introduction

认识 React

React 是 facebook 开源的一套框架,可总结为以下几个特点:

  • 基于 JSX 语法糖实现
  • JUST THE UI:在多层架构的设计模式中,React 并不算 MVC 的架构,它可理解为 MVC 的 V 层。
  • VIRTUAL DOM:虚拟DOM,是轻量的 js 对象,只保留了原生 dom 的一些常用的属性和方法。
  • DATA FLOW: React是单向响应的数据流。

技术点

题外话

这份教程是综合了Y.PigDK的内容,因为代码和文档比较多,整理中如有疏漏或者错误,可以在 Issues 中提出,多多谅解,希望对你们有帮助,如果你喜欢可以点个 Star 或者 Fork ,谢谢~

react-tutorial's People

Contributors

27c4 avatar dependabot[bot] avatar wscats avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-tutorial's Issues

React利用events模块实现组件通信

安装

首先需要项目中安装events

npm install events --save

image

在src下新建一个util目录里面建一个events.js

import { EventEmitter } from 'events';
export default new EventEmitter();

image

使用

原理是使用events模块的自定义事件机制

componentDidMount事件中,如果组件挂载完成,再订阅事件;在组件卸载的时候,在componentWillUnmount事件中取消事件的订阅;
以常用的发布/订阅模式举例,借用Node.js Events模块的浏览器版实现

在一个组件的componentDidMount生命周期中中监听事件,并在componentWillUnmount生命周期中销毁

import emitter from '../../util/events';
componentDidMount() {
	// 组件装载完成以后声明一个自定义事件
	this.eventEmitter = emitter.addListener('changeMessage', (message) => {
		this.setState({
			message,
		});
	});
}
componentWillUnmount() {
	emitter.removeListener(this.eventEmitter);
}

然后在另外一个组件中触发该事件

import emitter from '../../util/events';
changeMessage() {
	emitter.emit('changeMessage', 'wscats');
}

参考文档

React中组件通信的几种方式

React组件生命周期

组件的生命周期

image

组件在初始化时会触发5个钩子函数:

id 钩子函数 用处
1 getDefaultProps() 设置默认的props,也可以用defaultProps设置组件的默认属性
2 getInitialState() 在使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以访问this.props
3 componentWillMount() 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state
4 render() react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了
5 componentDidMount() 组件渲染之后调用,可以通过this.getDOMNode()获取和操作dom节点,只调用一次

在更新时也会触发5个钩子函数:

id 钩子函数 用处
6 componentWillReceivePorps(nextProps) 组件初始化时不调用,组件接受新的props时调用
7 shouldComponentUpdate(nextProps, nextState) react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候。不过调用this.forceUpdate会跳过此步骤
8 componentWillUpdate(nextProps, nextState) 组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state
9 render() 同上
10 componentDidUpdate() 组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点。还有一个卸载钩子函数
11 componentWillUnmount() 组件将要卸载时调用,一些事件监听和定时器需要在此时清除。

以上可以看出来react总共有10个周期函数(render重复一次),这个10个函数可以满足我们所有对组件操作的需求,利用的好可以提高开发效率和组件性能

React-Native

安装

我们推荐使用Homebrew来安装NodeWatchman。在命令行中执行下列命令安装:

brew install node
brew install watchman

如果你已经安装了 Node,请检查其版本是否在 v8.3 以上。安装完 Node 后建议设置 npm 镜像以加速后面的过程(或使用科学上网工具)。

注意:不要使用 cnpm!cnpm 安装的模块路径比较奇怪,packager 不能正常识别!

npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

Yarn、React Native 的命令行工具(react-native-cli)
Yarn是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载。React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务

npm install -g yarn react-native-cli

安装完 yarn 后同理也要设置镜像源:

yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global

创建项目

使用 React Native 命令行工具来创建一个名为AwesomeProject的新项目:

react-native init AwesomeProject
cd AwesomeProject && npm install

启动

进入ios文件夹用Xcode打开AwesomeProject.xcodeproj文件,并启动
屏幕快照 2019-06-30 上午7 13 52

props

大多数组件在创建时就可以使用各种参数来进行定制。用于定制的这些参数就称为props属性。

class Greeting extends Component {
  render() {
    return (
      <View style={{alignItems: 'center', marginTop: 50}}>
        <Text>Hello {this.props.name}!</Text>
      </View>
    );
  }
}

type Props = {};
export default class App extends Component<Props> {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>Welcome1 to React Native!</Text>
        <Text style={styles.instructions}>To get started, edit App.js</Text>
        <Text style={styles.instructions}>{instructions}</Text>
        <Greeting name='Rexxar' />
        <Greeting name='Jaina' />
        <Greeting name='Valeera' />
      </View>
    );
  }
}

布局

在组件样式中使用flex可以使其在可利用的空间中动态地扩张或收缩。一般而言我们会使用flex:1来指定某个组件扩张以撑满所有剩余的空间。如果有多个并列的子组件使用了flex:1,则这些子组件会平分父容器中剩余的空间。如果这些并列的子组件的flex值不一样,则谁的值更大,谁占据剩余空间的比例就更大(即占据剩余空间的比等于并列组件间flex值的比)。

屏幕快照 2019-06-30 下午9 55 37

<View style={{ flex: 1 }}>
    <View style={{ flex: 1, backgroundColor: 'powderblue' }} />
    <View style={{ flex: 2, backgroundColor: 'skyblue' }} />
    <View style={{ flex: 3, backgroundColor: 'steelblue' }} />
</View>

组件

可以新建src/components 文件夹新建Greeting.js,并在App.js中引入import Greeting from './components/Greeting/Greeting.js';,并使用<Greeting name='Eno Yao’ />

import React, { Component } from 'react';
import { Text, View } from 'react-native';
export default class Greeting extends Component {
    render() {
        return (
            <View style={{ alignItems: 'center', marginTop: 50 }}>
                <Text>Hello {this.props.name}!</Text>
            </View>
        );
    }
}

事件

点击这个按钮会调用onPress函数,具体作用就是显示一个 alert 弹出框。

react react-native
onClick onPress
onChange onChangeText
import React, { Component } from 'react';
import { Text, View, Button, Alert } from 'react-native';
export default class Greeting extends Component {
    render() {
        return (
            <View style={{ alignItems: 'center', marginTop: 50 }}>
                <Text>Hello {this.props.name}!</Text>
                <Button
                    onPress={() => {
                        Alert.alert("你点击了按钮!");
                    }}
                    title="点我!"
                />
            </View>
        );
    }
}

处理文本输入

TextInput是一个允许用户输入文本的基础组件。它有一个名为onChangeText的属性,此属性接受一个函数,而此函数会在文本变化时被调用。另外还有一个名为onSubmitEditing的属性,会在文本被提交后(用户按下软键盘上的提交键)调用。

<TextInput
    style={{ height: 40 }}
    placeholder="Type here to translate!"
    onChangeText={(text) => this.setState({ text })}
/>
<TextInput
    // style={{ height: 40 }}
    placeholder="Type here to translate!"
    onChangeText={this.onChangeText.bind(this)}
/>
// js
onChangeText(text) {
    console.log(text)
    this.setState({ text })
}

导航

在你的React Native项目中安装react-navigation这个包

yarn add react-navigation
# or with npm
# npm install --save react-navigation

然后,安装react-native-gesture-handler

yarn add react-native-gesture-handler
# or with npm
# npm install --save react-native-gesture-handler

Link所有的原生依赖,这一步是必须的,之前漏了这一步在这里花费了很多时间

屏幕快照 2019-06-30 上午9 42 41

react-native link react-native-gesture-handler
# or
react-native link

App.js中把代码替换为下面这些

import React, { Component } from 'react';
import { Text, View, Button } from 'react-native';
import { createAppContainer, createStackNavigator, StackActions, NavigationActions } from 'react-navigation'; // Version can be specified in package.json

class HomeScreen extends Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Home Screen</Text>
        <Button
          title="Go to Details"
          onPress={() => {
            this.props.navigation.dispatch(StackActions.reset({
              index: 0,
              actions: [
                NavigationActions.navigate({ routeName: 'Details' })
              ],
            }))
          }}
        />
      </View>
    );
  }
}

class DetailsScreen extends Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Details Screen</Text>
      </View>
    );
  }
}

const AppNavigator = createStackNavigator({
  Home: {
    screen: HomeScreen,
  },
  Details: {
    screen: DetailsScreen,
  },
}, {
    initialRouteName: 'Home',
  });

export default createAppContainer(AppNavigator);

导航跳转

<Button
  title="Go to Home"
  onPress={() => this.props.navigation.navigate('Home')}
/>

将路由页面封装模块化封装进组件里面

import { createAppContainer, createStackNavigator } from 'react-navigation'; // Version can be specified in package.json

import DetailsScreen from './src/components/DetailsScreen/DetailsScreen'
import HomeScreen from './src/components/HomeScreen/HomeScreen'

const AppNavigator = createStackNavigator({
  Home: {
    screen: HomeScreen,
  },
  Details: {
    screen: DetailsScreen,
  },
}, {
    initialRouteName: 'Home',
});

export default createAppContainer(AppNavigator);

导航栏标题

屏幕快照 2019-06-30 下午8 52 54

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Home',
  };
}

导航栏按钮

屏幕快照 2019-06-30 下午9 08 51

class LogoTitle extends React.Component {
    render() {
        return (
            <Image
                source={require('../../assets/spiro.png')}
                style={{ width: 30, height: 30 }}
            />
        );
    }
}
export default class DetailsScreen extends Component {
    static navigationOptions = {
        title: 'Detail',
        headerTitle: <LogoTitle />,
        headerRight: (
            <Button
                title="菜单"
                onPress={() => alert('This is a button!')}
                title="Info"
                color="#58bc58"
            />
        ),
    };
    // other code
}

底部导航条

Kapture 2019-07-02 at 13 19 46

import { createAppContainer, createStackNavigator, createBottomTabNavigator } from 'react-navigation'; // Version can be specified in package.json

import DetailsScreen from './src/components/DetailsScreen/DetailsScreen'
import HomeScreen from './src/components/HomeScreen/HomeScreen'
import MineScreen from './src/components/MineScreen/MineScreen'


const MineNavigator = createStackNavigator({
  Mine: {
    screen: MineScreen,
  },
  Details: {
    screen: DetailsScreen,
  },
}, {
    initialRouteName: 'Mine',
  });

const HomeNavigator = createStackNavigator({
  Home: {
    screen: HomeScreen,
  },
  Details: {
    screen: DetailsScreen,
  },
}, {
    initialRouteName: 'Home',
  });

const TabNavigator = createBottomTabNavigator({
  Home: HomeNavigator,
  Mine: MineNavigator,
}, {
    tabBarOptions: {
      activeTintColor: 'tomato',
      inactiveTintColor: 'gray',
    },
  });
export default createAppContainer(TabNavigator);

网络

React Native 提供了和 web 标准一致的Fetch API,用于满足开发者访问网络的需求。如果你之前使用过XMLHttpRequest(即俗称的 ajax)或是其他的网络 API,那么 Fetch 用起来将会相当容易上手。

async componentDidMount() {
    try {
      let response = await fetch('https://cnodejs.org/api/v1/topics');
      console.log(response.json());
    } catch (error) {
      console.error(error);
    }
}
// or
async componentDidMount() {
    var request = new XMLHttpRequest();
    request.onreadystatechange = (e) => {
      if (request.readyState !== 4) {
        return;
      }
      if (request.status === 200) {
        console.log('success', request.responseText);
      } else {
        console.warn('error');
      }
    };
    request.open('GET', 'https://cnodejs.org/api/v1/topics');
    request.send();
}

基础组件

Picker

Kapture 2019-06-30 at 17 50 24

本组件可以在iOS和Android上渲染原生的选择器Picker

import React, { Component } from 'react';
import { Text, View, Picker } from 'react-native';
export default class HomeScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      language: 'java',
      itemIndex: 0,
      pickers: [{
        label: 'Java',
        value: 'java'
      }, {
        label: 'JavaScript',
        value: 'js'
      }, {
        label: 'Python',
        value: 'py'
      }]
    };

    this.setDate = this.setDate.bind(this);
  }
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Picker
          selectedValue={this.state.language}
          style={{ height: 50, width: 100 }}
          onValueChange={(itemValue, itemIndex) => this.setState({ language: itemValue, itemIndex })}>
          {
            this.state.pickers.map((item, index) => {
              return (<Picker.Item key={index} label={item.label} value={item.value} />)
            })
          }
        </Picker>
        <Text>{this.state.language}{this.state.itemIndex}</Text>
      </View>
    );
  }
  setDate(newDate) {
    this.setState({ chosenDate: newDate })
  }
}

ScrollView && FlatList

Kapture 2019-06-30 at 18 30 12

能实现下拉刷新

import React, { Component } from 'react';
import { Text, ScrollView, FlatList, StyleSheet, RefreshControl } from 'react-native';
export default class HomeScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      refreshing: false,
    };
  }
  _onRefresh = () => {
    this.setState({ refreshing: true });
    setTimeout(() => {
      this.setState({ refreshing: false });
    }, 2000)
  }
  render() {
    return (
      <ScrollView refreshControl={
        <RefreshControl
          title='下拉刷新'
          titleColor='#58bc58'
          refreshing={this.state.refreshing}
          onRefresh={this._onRefresh}
        />} contentContainerStyle={styles.contentContainer}>
        <Text style={styles.TextStyle}>1</Text>
      </ScrollView>
    );
  }
}
const styles = StyleSheet.create({
  contentContainer: {
    paddingVertical: 20,
    padding: 20
  },
  TextStyle: {
    color: 'red',
    borderStyle: 'solid'
  }
});

Kapture 2019-06-30 at 20 18 21

FlatList更适于长列表数据,且元素个数可以增删。和ScrollView不同的是,FlatList并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。

FlatList组件必须的两个属性是data和renderItem。data是列表的数据源,而renderItem则从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染。

import React, { Component } from 'react';
import { Text, ScrollView, FlatList, StyleSheet, RefreshControl } from 'react-native';
export default class HomeScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      refreshing: false,
      data: []
    };
  }
  async _onRefresh() {
    this.setState({ refreshing: true });
    var self = this;
    var request = new XMLHttpRequest();
    request.onreadystatechange = (e) => {
      if (request.readyState !== 4) {
        return;
      }
      if (request.status === 200) {
        console.log('success', JSON.parse(request.responseText).data);
        self.setState({
          data: this.state.data.concat(JSON.parse(request.responseText).data)
        })
        self.setState({ refreshing: false });
      } else {
        console.warn('error');
      }
    };
    request.open('GET', 'https://cnodejs.org/api/v1/topics');
    request.send();
  }
  async componentDidMount() {
    this._onRefresh();
  }
  render() {
    return (
      <FlatList refreshControl={
        <RefreshControl
          title='下拉刷新'
          titleColor='#58bc58'
          refreshing={this.state.refreshing}
          onRefresh={this._onRefresh.bind(this)}
        />} contentContainerStyle={styles.contentContainer}
        data={this.state.data}
        renderItem={({ item }) => <Text style={styles.item}>{item.title}</Text>}
        keyExtractor={(item) => item.id}
      />
    );
  }
}
const styles = StyleSheet.create({
  contentContainer: {
    paddingVertical: 20,
    padding: 20
  },
  TextStyle: {
    color: 'red',
    borderStyle: 'solid'
  }
});

实现上拉刷新

<FlatList
        onEndReached={this.onEndReached.bind(this)}
        onEndReachedThreshold="0.05"
/>

参考文档

React脚手架生成配置文件

生成config目录

在 create-react-app 的 package.json 文件里面有一行命令

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
},

所以当我们执行npm run eject的时候,我们就可以在我们的项目中生成一个config的配置文件,里面可以更改 webpack 的参数

2018-12-03 3 02 23

配置sass

经过上面的操作之后,我们可以在项目中使用 sass 由于 sass 的配置环境已经给我们配置好了

// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// code

{
      test: sassRegex,
      exclude: sassModuleRegex,
      use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader'),
},

所以我们可以直接在命令行里面安装node-sass就可以在 react 项目中使用 sass 了

配置Proxy代理

由于我们在项目中经常会出现跨域,我们可以在 package.json 文件中,用 proxy 来设置服务器代理

"proxy": "http://news-at.zhihu.com",

在项目中当你就可以发请求代理到对应的连接上

React.axios.get('/api/jsons/')
    .then((response) => {
        console.log(response);
        this.setState({
            cards: response.data.data.cards
        })
    })
    .catch(function (error) {
        console.log(error);
    });

以上连接就会从/api/jsons/代理到https://news-at.zhihu.com/api/jsons/访问数据

React-Router

参考文档

React-router英文文档
初探 React Router 4.0
React-router中文文档

安装

npm install react-dom
npm install react-router-dom

DEMO如下

var React = require('react');
var ReactDom = require('react-dom');
import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
//先获取组件(创建组件)再使用
var AppComponent = require('./components/productBox.jsx');
var Wscats = require('./components/wscats.jsx');

ReactDom.render((
  <Router>
    {/*rouer只能有一个子标签,所以用div包起来*/}
    <div>
      <ul>
        <li>
          <Link to="/">/</Link>
        </li>
        <li>
          <Link to="/index">index</Link>
        </li>
      </ul>
      <Route exact path="/" component={AppComponent}/>
      <Route path="/index" component={Wscats}/>
    </div>
  </Router>
), document.getElementById('content'))
引用 作用
react-router React Router 核心
react-router-dom 用于DOM绑定的React Router
react-router-native 用于React Native的React Router
react-router-redux React Router和Redux的集成
react-router-config 静态路由配置的小助手

react-router和react-router-dom的区别

在React的使用中,我们一般要引入两个包,react 和react-dom,react-router和react-router-dom并不是两个都要引用的
他们两个只要引用一个就行了,不同之处就是后者比前者多出了<Link><BrowserRouter>这样的 DOM 类组件。
因此我们只需引用react-router-dom这个包就行了。当然,如果搭配 redux,你还需要使用react-router-redux或者react-redux

路由传参

状态 方式
Route component 以this.props.match方式
Route render 以({ match })=>()方式
Route children 以({ match })=>()方式
withRouter 以this.props.match方式

BrowserRouter和HashRouter的区别

react-router4文档用的是BrowserRouter,BroswerRouter是需要服务端配合的,服务端重定向到首页,BrowserRouter是基于html5的pushStatereplaceState的,很多浏览器不支持,存在兼容性问题。所以新手推荐使用HashRouter

BrowserRouter

BrowserRouter在不兼容的浏览器上会有诸多问题,比如路由嵌套的时候刷新页面空白无法直接进入嵌套路由,路由跳转也有可能不成功

import {BrowserRouter as Router, Route, Link, Redirect} from 'react-router-dom'
import {createBrowserHistory} from 'history'
const history = createBrowserHistory()
<Router history={history}>
//code
</Router>
//后面就可以使用history.push('/wscats');跳转地址为 url+/wscats

HashRouter

import {HashRouter, Route, Link, Redirect} from 'react-router-dom'
import {createHashHistory} from 'history'
const history = createHashHistory()
<HashRouter history={history}>
//code
</HashRouter>
//后面就可以使用history.push('/wscats');跳转地址为 url+#/wscats

路由嵌套

父路由要这么写

<Provider store={store}>
    <Router>
        <div>
            <Route path="/" exact component={Whome} />
            <Route path="/home" component={Whome} />
            <Route path="/detail/" component={Wdetail} />
        </div>
    </Router>
</Provider>

子路由,嵌套路由/home/hot,记得加上/home

<Route path="/home/hot" component={Wchanel}  />
<Route path="/home/fresh" component={Wchanel}  />

React使用Dva构建应用

安装

全局安装dva-cli脚手架,会应有一个新的dva命令,使用它来初始化一个新的dva项目,然后它会自动安装所需的依赖,然后进入该项目文件夹,然后执行npm start启动项目

npm install dva-cli -g
dva -v
dva new dva-quickstart
cd dva-quickstart
npm start

路由

routes目录下新建Products.jsx组件

import React from 'react';
// 是帮你连接组件和redux
import { connect } from 'dva';
// 这是dispatch和products是来自于props的方法,是connect后提供的
// {dispatch, products}等价于props.dispatch和props.products
const Products = ({ dispatch, products }) => {
  function handleDelete(id, e) {
    // 获取按钮传递的数据
    console.log(id, e)
    // 获取Model的state的数据
    console.log(products)
    dispatch({
      // 触发Model中的action方法
      type: 'products/delete',
      // 这里是传过去Model的数据
      payload: {
        name: "lemon",
        age: 18,
        skill: ['ps', 'css', 'js']
      },
    });
  }
  return (<div>
    <h2>Dva连接Redux的示例</h2>
    <button onClick={handleDelete.bind(this, 1)}>连接Model</button>
  </div>)
};

export default connect(({ products }) => ({
  products,
}))(Products);

在上面的路由组件中我们看到connect,由于dva提供了connect 方法,这个 connect就是react-reduxconnect方法,帮助我们建立组件和Model之间关系

然后添加路由信息到路由表,编辑router.js

import Products from './routes/Products';
<Route path="/products" exact component={Products} />

Model

可以使用Model来封装一个类似于react-redux的数据仓库,来交互组件之间的数据

models目录下新建products.js文件

export default {
  namespace: 'products',
  // state: {
  //   author: "wscats"
  // },
  state:{},//如果这里为空可以在index.js的dva()方法中初始化值
  reducers: {
    'delete'(state, {
      payload
    }) {
      // state是Model里面存放的state数据
      // payload是获取来自于Product.jsx组件的dispatch传过来的payload参数
      console.log("触发了redux", state, payload)
      // 这里是返回新的state数据
      return {
        ...state,
        ...payload
      }
    },
    // 可以存放多个action的方法
    add(state, {
      payload
    }) {

    }
  },
};
  • namespace 表示在全局 state 上的 key
  • state 是初始值,在这里是空数组
  • reducers 等同于 redux 里的 reducer,接收 action,同步更新 state

然后在index.js文件里面载入它

// 3. Model
app.model(require('./models/products').default);

如果已经在state里面初始化了数据,那就可以忽略下面这一步,但如果state没有定义值,可以在index.js中的dva()方法中初始化值

// 1. Initialize
const app = dva({
  initialState: {
    products: {
      author: "Eno Yao"
    }
  }
});

React实现双向数据绑定

利用Proxy对数据进行劫持,自动触发this.setState

import { define, WeElement, html, h, extend, get, set } from "omi";

export interface appOptions {
    name: string;
    view(props?: any, state?: any, ref?: any);
    state?: any;
    propTypes?: any;
    props?: any;
    defaultProps?: any;
    css?: string;
    install?(props?: any, state?: any, ref?: any);
    installed?(props?: any, state?: any, ref?: any);
    uninstall?(props?: any, state?: any, ref?: any);
    beforeUpdate?(props?: any, state?: any, ref?: any);
    updated?(props?: any, state?: any, ref?: any);
    beforeRender?(props?: any, state?: any, ref?: any);
    receiveProps?(props?: any, state?: any, ref?: any);
}

// Added code
const isObject = obj => Object.prototype.toString.call(obj) === '[object Object]';
// Added code
const observify = (obj, that) => {
    if (!isObject(obj)) {
        return obj;
    }
    Object.keys(obj).forEach(key => {
        obj[key] = observify(obj[key], that);
    });

    return Observer(obj, that);
}
// Added code
const Observer = (obj, that) => {
    return new Proxy(obj, {
        set(obj, prop, value) {
            const res = Reflect.set(obj, prop, value);
            that.update();
            return res;
        }
    });
}

const app = ({
    name,
    view,
    state,
    propTypes,
    props,
    defaultProps,
    css,
    install,
    installed,
    uninstall,
    beforeUpdate,
    updated,
    beforeRender,
    receiveProps
}: appOptions) => {
    define(name, class extends WeElement {
        state = Object.assign({}, state);

        static props = props;
        static propTypes = propTypes;
        static defaultProps = defaultProps;
        static css = css;
        private ref;

        constructor() {
            super();
            (that => {
                // Added code
                that.state = observify(that.state, that)
            })(this);
        }

        install() {
            install &&
                install(
                    this.props ? this.props : {},
                    this.state ? this.state : {},
                    this
                );
        }

        installed() {
            installed &&
                installed(
                    this.props ? this.props : {},
                    this.state ? this.state : {},
                    this
                );
        }

        uninstall() {
            uninstall &&
                uninstall(
                    this.props ? this.props : {},
                    this.state ? this.state : {},
                    this
                );
        }

        render(oProps, oState) {
            return view(
                this.props ? this.props : oProps,
                this.state ? this.state : oState,
                this
            );
        }
    });
};

extend("model", (el: any, path, scope) => {
    el.value = get(scope, path);
    el.addEventListener("input", () => {
        set(scope, path, el.value);
        scope.update();
    });
});

export { html, h, app };

Atom开发React的插件

atom-beautify

快速格式化代码,支持多种语言

ctrl+alt+b

language-javascript-jsx

jsx格式的代码有高亮效果

React+Webpack配置

项目结构

--your project
  |--app
    |--components
      |--productBox.jsx
    |--main.js
  |--build
    |--index.html
    |--bundle.js(该文件是webpack打包后生成的)

用npm安装react、webpack

默认已经安装了Nodejs,推荐用cnpm

npm install --save-dev react react-dom --save-dev
npm install -g webpack --save-dev//建议webpack全局安装,方便我们后面使用webpack命令

image

安装和配置Babel

Babel其实是一个编译JavaScript的平台,它的强大之处表现在可以通过编译帮你达到以下目的:

下一代的JavaScript标准(ES6,ES7),这些标准目前并未被当前的浏览器完全的支持;
使用基于JavaScript进行了拓展的语言,比如React的JSX
Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,不过webpack把它们整合在一起使用,但是对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-preset-es2015包和解析JSX的babel-preset-react包)。

npm install --save-dev babel-core babel-loader  babel-preset-es2015 babel-preset-react

在项目根目录下新建.babelrc文件,就是只有后缀名的文件,添加如下代码

//.babelrc
{
  "presets": [
    "react",
    "es2015"
  ]
}

安装其他loader

讲到这里,我们基本上就可以迅速搭建一个简单的web项目,但不得不提的是webpack loader。它是我个人认为相比于其他模块加载更牛X的地方,将它用于react的开发,结合react与生俱来的优越性能,两者天衣无缝的配合简直就是黄金组合。

总的来说webpack loader可以实现:

可以将React JSX语法转为js语句
React开发中支持ES6语法
支持通过import来直接引入css、less、sass甚至是图片
支持css中引用的图片大小在某一大小范围之内直接转为BASE64格式等等等

为了能够让以上功能奏效,我们要先安装对应的:
babel-loader

npm install babel-loader --save-dev
//css-loader
npm install css-loader --save-dev
//less-loader
npm install less-loader --save-dev
//style-loader
npm install style-loader --save-dev
//url-loader
npm install url-loader --save-dev

而具体的实现,我们只要在下面webpack的配置文件中加入module属性里的loaders:

配置webpack.config.js

webpack.config.js是webpack的配置文件

//__dirname是node.js中的一个全局变量,它指向当前执行脚本所在的目录
module.exports = { //注意这里是exports不是export
	devtool: 'eval-source-map', //生成Source Maps,这里选择eval-source-map
	entry: __dirname + "/app/main.js", //唯一入口文件
	output: { //输出目录
		path: __dirname + "/build", //打包后的js文件存放的地方
		filename: 'bundle.js', //打包后的js文件名
	},
	module: {
		loaders: [{
			test: /\.jsx?$/,
			exclude: /node_modules/, //屏蔽不需要处理的文件(文件夹)(可选)
			loader: 'babel-loader'
			//npm install babel-loader 
			//npm install babel-core
		}, {
			test: /\.css$/,
			loader: 'style-loader!css-loader'
		}, {
			test: /\.less$/,
			loader: 'style-loader!css-loader!less-loader'
		}, {
			test: /\.(png|jpg)$/,
			loader: 'url-loader?limit=25000'
		}]
	}
};

使用Source Maps,使调试更容易

devtool选项 配置结果
source-map 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包文件的构建速度
cheap-module-source-map 在一个单独的文件中生成一个不带列映射的map,不带列映射提高项目构建速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便
eval-source-map 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。不过在开发阶段这是一个非常好的选项,但是在生产阶段一定不要用这个选项
cheap-module-eval-source-map 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点

组件productBox.jsx

新版本推荐使用ES6书写React组件

var React = require('react');
//旧版本的写法,会有警告
/*var ProductBox = React.createClass({
	render: function() {
		return( 
			<div className = "productBox" >
				Hello World!
			</div>
		);
	}
});*/
//新版本的写法 推荐
class ProductBox extends React.Component {
	render() {
		return( 
			<div>Hello World!</div>
		)
	}
}
module.exports = ProductBox;

用旧版本写法会出现以下警告
image

前端页面index.html

index.html是最终要呈现的页面文件,代码如下

<!DOCTYPE html>
<html>
	<head lang="en">
		<meta charset="UTF-8">
		<title>React Test</title>
	</head>
	<body>
                <!--要插入React组件的位置-->
		<div id="content"></div>
		<script src="bundle.js"></script>
	</body
</html>

入口文件main.js

main.js是入口文件,用来将React组件放在真正的html中

var React = require('react');
var ReactDom = require('react-dom');
var AppComponent = require('./components/productBox.jsx');
ReactDom.render(
	<AppComponent / > 
	, document.getElementById('content')
);

依赖的描述文件package.json

package.json是一个标准的npm说明文件,里面蕴含了丰富的信息,包括当前项目的依赖模块,自定义的脚本任务等

{
  "devDependencies": {
    "babel-core": "^6.24.1",
    "babel-loader": "^7.0.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "less-loader": "^4.0.3",
    "react": "^15.5.4",
    "react-dom": "^15.5.4",
    "style-loader": "^0.16.1",
    "url-loader": "^0.5.8",
    "webpack": "^2.4.1"
  }
}

执行打包

在命令行执行webpack命令
这里写图片描述

安装并启用webpack-dev-server

想不想让你的浏览器监测你都代码的修改,并自动刷新修改后的结果,其实Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖

npm install --save-dev webpack-dev-server

在webpack.config.js增加devServer的配置

//__dirname是node.js中的一个全局变量,它指向当前执行脚本所在的目录
module.exports = { //注意这里是exports不是export
	devtool: 'eval-source-map', //生成Source Maps,这里选择eval-source-map
	entry: __dirname + "/app/main.js", //唯一入口文件
	output: { //输出目录
		path: __dirname + "/build", //打包后的js文件存放的地方
		filename: 'bundle.js', //打包后的js文件名
	},
	module: {
		loaders: [{
			test: /\.jsx?$/,
			exclude: /node_modules/, //屏蔽不需要处理的文件(文件夹)(可选)
			loader: 'babel-loader'
				//npm install babel-loader 
				//npm install babel-core
		}, {
			test: /\.css$/,
			loader: 'style-loader!css-loader'
		}, {
			test: /\.less$/,
			loader: 'style-loader!css-loader!less-loader'
		}, {
			test: /\.(png|jpg)$/,
			loader: 'url-loader?limit=25000'
		}]
	},
	devServer: {
		contentBase: './build', //默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到"build"目录)
		historyApiFallback: true, //在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html
		inline: true, //设置为true,当源文件改变时会自动刷新页面
		port: 8080, //设置默认监听端口,如果省略,默认为"8080"
	}
};

这里写图片描述
在命令行执行webpack-dev-server命令就会运行服务器

webpack-dev-server

如果需要停止服务,在终端按ctrl+c

React的prop-types

image
新版本的react的prop-type已经单独成包了,所以要先安装prop-types模块

npm install prop-types --save-dev

image
然后在组件中引入PropTypes模块

import React from 'react';
import PropTypes from 'prop-types';
class Skill extends React.Component {
//code
constructor(props) {
        super(props);
        // 设置 initial state
        this.state = {
            text: props.initialValue || 'Hello Wscats'
        };
    }
}

注意这里以前是React.PropTypes,现在为PropTypes

Skill.propTypes = {
	//定义传入props中的属性各种类型
    initialValue: PropTypes.string.isRequired//PropTypes.string
};
Skill.defaultProps = {
	//组件默认的props对象
    initialValue: 'Hello Oaoafly'
};

React虚拟DOM和DIFF算法

VDOM

VDOM,也叫虚拟 DOM,它是仅存于内存中的 DOM,因为还未展示到页面中,所以称为 VDOM

var vdom = document.createElement("div");

上面这一句就是最简单的虚拟 DOM

var vdom = document.createElement("div");
document.body.append(vdom);

上面这两句就是把虚拟 DOM 转化为 真实 DOM,其实就是把节点 append 到页面中

常见DOM操作

常见DOM操作,就三类:增、删、改。对应的DOM操作如下:

DOM操作 DOM方法
增加一个节点 appendChild
删除一个节点 removeChild
更改一个节点 replaceChild

以前我们写代码经常会拼接完模板,简单粗暴的用$(el).html(template)整块节点替换

这样做最大的问题在于性能,如果页面比较小,问题不大,但如果页面庞大,这样会出现卡顿,用户体验会很差,所以解决办法就是差量更新

差量更新

差量更新就是只对局面的 HTML 片段进行更新。比如你加了一个节点,那么我就只更新这个节点,我无需整个模板替换。这样做效率就会提高。但问题在于,不知道哪个节点更新了,哪个节点删除了,哪个节点替换了,所以我们需要对 DOM 建模

DOM 建模,简单点说就是用一个 JS 对象来表示 VDOM。

如果我们可以用一个JS对象来表示 VDOM,那么这个对象上多一个属性(增加节点),少一个属性(删除节点),或者属性值变了(更改节点),就很清醒了

DOM 也叫 DOM 树,是一个树形结构,DOM 树上有很多元素节点,要对 VDOM 进行建模,本质上就是对一个个元素节点进行建模,然后再把节点放回 DOM 树的指定位置

JSX建模

每个节点都是由以下三部分组成

  • type : 元素类型
  • props : 元素属性
  • children : 子元素集合
{type:"div",props: null, children:[
       {type:"img",props:{"src":"avatar.png", "className":"profile"},children:[],
       {type:"h3",props: null, children:[{[user.firstName, user.lastName].join(' ')}],
]}

上面 VDOM 建模是用下面的 HTML 结构转出来的

var profile = <div>
  <img src="avatar.png" className="profile" />
  <h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;

但这段代码并不是合法的 js 代码,它是一种被称为 jsx 的语法扩展,通过它我们就可以很方便的在 js 代码中书写 html 片段

本质上,jsx 是语法糖,上面这段代码会被 babel 转换成如下代码

pig("div", null, pig("img", {
    src: "avatar.png",
    className: "profile"
}), pig("h3", null, [user.firstName, user.lastName].join(" ")))

而上面的这段被转化的代码是 将我们的 VDOM 配合pig(一般应该是createElement函数)转化为真实 DOM

注意,如果是自定义组件<App />会转化为pig(App, null),因为组件是class App extends React.Component {}这样定义的,所以App进入createElement函数里面就会变成是一个对象

这里我们可以把这个函数放进createElement()里面生成一个 VDOM 对象,然后用生成的 VDOM 对象,配合render()生成一个 DOM 插入页面,从而转变成真实 DOM 结构

createElement()

补充createElement()方法的源代码

function createElement(type, props, ...childrens) {
    return {
        // 父标签类型,比如dev,ul等
        type: type,
        // 属性值
        props: {
            ...props,
        },
        // 子节点,比如li,字符串等
        children: childrens.length <= 1 ? childrens[0] : childrens
    };
}

render()

补充render()方法的源代码

//=>DOM的动态创建
function render(jsxObj, container, callback) {
    let {
        type,
        props,
        children
    } = jsxObj;
    let newElement = document.createElement(type);
    //=>属性和子元素的处理
    for (let attr in props) {
        if (!props.hasOwnProperty(attr)) break;
        switch (attr) {
            case 'className':
                newElement.setAttribute('class', props[attr]);
                break;
            case 'style':
                let styleOBJ = props['style'];
                for (let key in styleOBJ) {
                    if (styleOBJ.hasOwnProperty(key)) {
                        newElement['style'][key] = styleOBJ[key];
                    }
                }
                break;
                // =>CHILDREN
            case 'children':
                // 如果children放在props里面的话,这句才会有意义
                // renderChildren()
            default:
                newElement.setAttribute(attr, props[attr]);
        }
    }
    renderChildren()

    function renderChildren() {
        let childrenAry = children;
        childrenAry = childrenAry instanceof Array ? childrenAry : (childrenAry ? [childrenAry] : []);
        childrenAry.forEach(item => {
            // 如果子节点直接是字符串,进入这个分支
            if (typeof item === 'string') {
                // =>字符串:文本节点,直接增加到元素中
                newElement.appendChild(document.createTextNode(item));
            } else {
                // 如果是标签节点比如<span><img />这些都进入这个分支
                // =>字符串:新的JSX元素,递归调用RENDER,只不过此时的容器是当前新创建的newElement
                render(item, newElement);
            }
        });
    }
    console.log(newElement);
    container.appendChild(newElement);
    callback && callback();
}

transform-react-jsx

安装transform-react-jsx来实现 jsx 和 js 之间的转换

npm install [email protected] @babel/core @babel/preset-env webpack // 首先安装好 babel 环境
npm install --save-dev babel-plugin-transform-react-jsx //再安装 transform-react-jsx 插件

配置对应的 webpack 参数,如果这里把注释的那条 plugins 打开,那就不需要写另外配置 .babelrc 文件,这里默认会先使用 plugins 的配置的

const path = require('path');

const config = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [{
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
                loader: 'babel-loader',
                options: {
                    // "plugins": ["transform-react-jsx"]// 如果需要配置参数注释这条,在 .babelrc上面配置
                }
            }
        }]
    }
};

module.exports = config;

配置 .babelrc 文件,如果需要对应的自定义的函数名,可以设置 pragma 的参数,不设置默认返回 React.createElement

{
  "plugins": [
    ["transform-react-jsx", {
      "pragma": "pig.yao" // default pragma is React.createElement
    }]
  ]
}

源码

参考文章

React-Redux

高阶组件(HOC)

redux的原理是利用了高阶组件,下面就是一个常见的高阶组件,高阶组件本质是利用了函数组件和类组件的特性实现的,函数组件可以得到外部props值,并传递给类组件,然后返回类组件,最后使用的这个函数组件就是高阶组件

import React, { Component } from 'react'
export default (props) => {
    return (WraooedComponent) => {
        return class HOC extends Component {
            state = {
                name: 'Yao',
                ...props
            }
            render() {
                return (
                    <div>
                        Hello World
                        <WraooedComponent name={{...this.state}} />
                    </div>
                )
            }
        }
    }
}

在其他组件中使用该高阶组件HOC()(App)

import React, { Component } from 'react'
import HOC from './components/HOC'
class App extends Component {
  render() {
    return ( )
  };
}

export default HOC({
  age: 18
})(App);

安装

在项目目录下安装这两个模块

npm install --save react-redux
npm install --save redux

引入模块

App.jsx文件中引入

//redux
import {Provider, connect} from 'react-redux';
import {createStore} from 'redux';

配置Action

// Action
const increaseAction = {
  type: 'increase'
}

const multiAction = {
  type: 'multi'
}

配置Reduce

在这里可以灵活运用...运算符或者Object.assign()

Object.assign()方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象

谨记永远不要修改state,而是返回一个全新的state

// Reducer
function counter(state = {
  count: 0
}, action) {
  const count = state.count
  switch (action.type) {
    case 'increase':
      return {
        ...state,
        count: count + 2
      }
    case 'multi':
      return Object.assign({}, state, {name: action.name});
    default:
      return state
  }
}

创建store

// Store
const store = createStore(counter);

配置Connect

语法

作用:连接React组件与 Redux store

connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])

mapStateToProps

mapStateToProps(state, ownProps)

这个函数允许我们将store中的数据作为props绑定到组件上

  1. 这个函数的第一个参数就是 Redux 的 store,你不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性,也就是说我们可以按需返回我们需要的store数据
  2. 函数的第二个参数 ownProps,是组件自己的 props,有的时候,ownProps也会对其产生影响。

当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给组件

mapDispatchToProps

mapDispatchToProps(dispatch, ownProps)

connect 的第二个参数是 mapDispatchToProps,它的功能是,将action作为props绑定到组件上

[mergeProps],[options]

不管是 stateProps还是dispatchProps,都需要和ownProps合并之后才会被赋给组件,所以组件既有state数据也有action方法,connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用Object.assign替代该方法

[options] (Object) 如果指定这个参数,可以定制 connector 的行为。一般不用

首先connect之所以会成功,是因为Provider组件:

  1. 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
  2. 接收Redux的store作为props,通过context对象传递给子孙组件上的connect
// Map Redux state to component props
function mapStateToProps(state) {
  return {
    value: state.count
  }
}

// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction)
  }
}

// Connected Component
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

在主文件main.js的集中写法

import React from 'react';
import ReactDOM from 'react-dom';
import 'react-bootstrap';
import Basic from "./components/basic.jsx";

//redux
import {Provider, connect} from 'react-redux';
import {createStore} from 'redux';

// Action
const increaseAction = {
  type: 'increase'
}

const multiAction = {
  type: 'multi'
}

// Reducer
function counter(state = {
  count: 0
}, action) {
  const count = state.count
  switch (action.type) {
    case 'increase':
      return {
        count: count + 2
      }
    case 'multi':
      return {
        count: count * 2
      }
    default:
      return state
  }
}

// Store
const store = createStore(counter);
// Map Redux state to component props
function mapStateToProps(state) {
  return {value: state.count}
}

// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => {
      //可以触发多个
      dispatch(increaseAction)
      dispatch(multiAction)
    }
  }
}

// Connected Component
const App = connect(mapStateToProps, mapDispatchToProps)(Basic)

ReactDOM.render((
  <Provider store={store}>
    <App/>
  </Provider>
), document.getElementById('root'))

Basic组件

import React from 'react';
import {BrowserRouter as Router, Route, Link, browserHistory} from 'react-router-dom'
//引入组件
import Home from "./home.jsx";
import Topics from "./topics.jsx";
import About from "./about.jsx";

// React component
class Basic extends React.Component {
  render() {
    const {value, onIncreaseClick} = this.props
    return (
      <Router history={browserHistory}>
        <div>
          <p>{value}</p>
          <button onClick={onIncreaseClick}>Increase</button>
          <ul>
            <li>
              <Link to="/">首页</Link>
            </li>
            <li>
              <Link to="/about">关于</Link>
            </li>
            <li>
              <Link to="/topics">主题列表</Link>
            </li>
          </ul>
          <hr/>
          <Route exact path="/" component={Home}/>
          <Route path="/about" component={About}/>
          <Route path="/topics" component={Topics}/>
        </div>
      </Router>
    )
  }
}

export default Basic;

组件通信

通过connect方法生成新的组件,让新的组件有权利访问和修改store中的数据,从而完成组件间的数据通信

import React from 'react';
import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
import {connect} from 'react-redux';
class Topics extends React.Component {
  render() {
    console.log(this)
    const {value, onIncreaseClick} = this.props;
    return (
      <div>
        <h2>主题列表</h2>
        <p>{value}</p>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    )
  }
}

// Action
const increaseAction = {
  type: 'increase'
}
const multiAction = {
  type: 'multi'
}
// Map Redux state to component props
function mapStateToProps(state) {
  return {value: state.count}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => {
      //可以触发多个
      dispatch(increaseAction)
      dispatch(multiAction)
    }
  }
}

// 最重要就是这句Connected Component让Topics也连接上store
module.exports = connect(mapStateToProps, mapDispatchToProps)(Topics);

参考案例

  1. react-redux-connect-demo
  2. simplest-redux-example
  3. React-redux中文文档

常见问题

  1. Can only update a mounted or mounting component问题

React组件

React组件

React提供了两种方式来声明组件,一种是函数式,一种是类式,就是用ES6的class我们所有的组件都继承自React.Component
函数式很简单,就像我们平常写函数一个,接受一个参数作为输入,然后进行相应的输出,只不过它输出的jsx格式

函数式组件

注意组件只能有一个根元素

function Wscats(props) {  
    return  <h1> {props.name}</h1>  
}

//ES6
const Wscats = ({props}) => (
  <div>
    <h1>{props.name}</h1>
  </div>
)

类式组件

import React from 'react';
//推荐这种
class Wscats extends React.Component {
  render() {
    return  <h1> {this.props.name}</h1>
  }
}
//or 这种方法将要废弃
var Wscats = React.createClass({
  render() {
    return  <h1> {this.props.name}</h1>
  }
}

渲染组件

HTML标签怎么使用,组件就怎么使用

HTML标签有两种使用方式

  1. 一种是img自闭合标签<img />
  2. 一种是h1之类的双标签<h1></h1>

同理,组件也有这两种使用方式。传参则像是给html标签写属性,属性名 = 属性值,如name =”Wscats” , 组件内部的props,则把这些属性组合在一起形成对象{name: "Oaoafly"}

<Wscats name="Oaoafly" />//标签一定要闭合,就是后面的/不能漏掉
<Wscats></Wscats>

使用组件,它返回了<h1>Oaoafly</h1>,很像html代码,其实它是React所说的虚拟DOM,并不是真实的DOM,我们要把虚拟DOM渲染成真实的DOM,才能显示到页面中,这需要用到ReactDOM的render方法,它接受的第一个参数,就是虚拟DOM,第二个参数就是我们要把DOM渲染到什么地方

ReactDOM.render(
    {/*虚拟DOM*/}
    <Wscats name="Oaoafly" />,
    {/*DOM渲染的地方*/}
    document.getElementById('root')
);

render()应该是一个纯粹函数,即使不使用组件的state,每次调用都返回同样的结果,不读写DOM,不与浏览器交互,只做最单纯的渲染。当需要修改状态或有交互时,应该在componentDidMount等组件生命周期函数中进行

Props传值

属性值是一个字符串,是最简单的方式进行传值,其实属性值可以是任何的JS表达式,只要把它们包括在{}中,在jsx中,{}里面的所有东西都当作JS表达式进行解析,比如我们向组件中传递一个数字18,我们就可以写age = {18}

function Wscats(props) {
  // 新增num 属性
  return <h1>
    {props.name}
    {props.age}</h1>
}

ReactDOM.render(
  <Wscats name="Oaoafly" age={16}/>, //JS表达式进行传值
    document.getElementById('root'));

如果要传递多个属性, 一个一个传会很麻烦,这时可以使用对象,但如果用对象进行传值,又不符合属性名=属性值的写法,这时要用到ES6中的扩展运算符...,React对ES6中的扩展运算符(…)进行扩展,它能运用到对象上,对对象进行分割,{…obj}

var obj = {
    name: "Wscats",
    age: 16
  }
{…obj} => name = "Wscats", age = 16,

正好对应父组件向子组件传递数据

var obj = { name: "Wscats", age : 16 } //函数式组件
function Wscats(props) { 
return (
    <div>
      <h1>{props.name}</h1>
      <h1>{props.age}</h1>
    </div>
)} // 渲染成真实的DOM
ReactDOM.render(
    <Wscats {...obj} />, document.getElementById('root') );

组件Props传对象

类式组件和函数组件可以互相组合

//类式组件
class Wscats extends React.Component {
  render() {
    return <h1>
      {this.props.name}</h1>
  }
}
//函数组件的组合
function App() {
  return (
    <div>
      <Wscats name="Oaoafly"/>
      <Wscats name="Eno"/>
    </div>
  );
}
ReactDOM.render(
  <App/>, //自闭合标签
    document.getElementById('root'));

这时有两点需要注意:

  1. 所有的组件名必须大写,以和普通的html标签进行区分
  2. 所有的组件都返回一个单一的根节点,这也是上面的render方式中把所有元素都放到一个div元素中的原因

类式组件和函数组件组合

组件状态

使用class声明组件有一个好处,就是组件内部可以有自己的状态,给组件添加一个内部状态,用的是类的构造函数,因为构造函数保存的就是实例身上的属性,可以看成这个实例(组件)的状态, 我们组件也可以使用这个状态

在这里一定要注意调用构造函数时,super()这是ES6的语法规定,子类中没有this, 只能调用super生成子类的 this,如果在调用super之前使用this,就会报错。 这个地方其实是不会变化的,可以看成一个模式。每次给组件添加状态的时候,我们就按照这个模式书写就可以了

//类式组件
class Wscats extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      date: new Date()
    } // 给组件添加状态
  }
  render() {
    return (
      <div>
        <h1>
          {this.props.name}</h1>
        <h2>现在时间是:{this.state.date.toLocaleTimeString()}</h2>
      </div>
    )
  }
}
//组件的组合
function App() {
  return (
    <div>
      <Wscats name="Oaoafly"/>
      <Wscats name="Eno"/>
    </div>
  );
}
ReactDOM.render(
  <App/>, //自闭合标签
    document.getElementById('root'));

所以往后在组件中添加状态,只要往this.state的对象中添加键值对就可以了

constructor(props){
    super(props);
    this.state = {name: "Hello Wscats"}  // 给组件添加状态
}

然后你就可以用表达式把state的状态呈现

<div>现在时间是:{this.state.date.toLocaleTimeString()}</div>

组件添加状态

注释

在render方法中添加注释,要用{/* */}方式

{/*要添加的注释*/}

生命周期

组件有状态,这涉及到组件的生命周期,react定义了非常完善的生命周期函数,组件渲染到页面中叫挂载(mounting),所以渲染完成后,叫做componentDidMount, 组件的卸载叫Unmount,所以组件将要卸载 叫做componentWillUnmount。我们想要在什么时候使用状态,就可以直接调用生命周期函数,把想要做的事情写到函数里面,生命周期函数直接写在组件内部

详情见 React组件生命周期

比如,页面渲染完成后时间自动加一秒,这时还要涉及到组件的状态更改。React不允许直接更改状态, 或者说,我们不能给状态(如: date)进行赋值操作, 必须调用组件的setState()方法去更改状态。这里写一个函数changeTime来更改状态

setState更改状态

changeTime函数也可以直接写到组件里面,根据ES6class语法的规定,直接写在类中的函数都会绑定在原型上,所以this.changeTime可以调用。但要保证this指向的是我们这个组件,而不是其他的东西, 这也是在setInterval中使用箭头函数的原因

//类式组件
class Wscats extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      date: new Date()
    } // 给组件添加状态
  }
  changeTime() {
    this.setState({date: new Date()})
  }
  // 生命周期函数
  componentDidMount() {
    setInterval(() => {
      this.changeTime()
    }, 1000)
  }
  render() {
    return (
      <div>
        <h1>
          {this.props.name}</h1>
        <h2>现在时间是:{this.state.date.toLocaleTimeString()}</h2>
      </div>
    )
  }
}
//组件的组合
function App() {
  return (
    <div>
      <Wscats name="Oaoafly"/>
      <Wscats name="Eno"/>
    </div>
  );
}

注意这里this的指向,如果不是箭头函数,this指向就是window

setInterval(function() {
  this.changeTime()
}.bind(this), 1000)
//or
setInterval(() => {
  this.changeTime()
}, 1000)

React中给元素添加事件,就像我们给元素添加行内事件一样简单,但这里也有不同的地方,事件名用驼峰命名法onClick,事件处理函数是函数名,用{}括起来

handleClick(e) {
    e.preventDefault();
    console.log(this);// null
    console.log("clicked");
}

在页面中点击可以看到handleClick中的事件处理函数console.log输出是null, 然后报错了。这主要是由于this是在函数运行的时候动态绑定的,this.handleClick指向了handleClick函数,点击的时候,这个函数开始执行,但this却没有指定,它是在哪个环境下执行, 由于ES6class是在严格模式下进行的,所以输出了null

我们必须使this指向我们这个组件,改变this指向可以使用两种方法,一种是ES5提供的bind()方法,它的第一个参数就是指定函数中this的,且它返回 一个函数,可以知道,返回的这个函数中this已经写死了,在程序运行的时候也不会变化了

this.handleClick = this.handleClick.bind(this)

解决方案1

也就是在构造函数中加上,此时调用this就可以查看修改到state的状态

constructor(props) {
  super(props);
  this.state = {
    date: new Date()
  } // 给组件添加状态
  this.handleClick = this.handleClick.bind(this)
}

解决方案2

这样也可以

<button onClick={this.handleClick.bind(this)}>点击</button>

解决方案3

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

组件的this指向

生命周期相关参数,是React定义组件时提供的一系列处理函数(钓子函数),这些函数会在组件生命周期的某个阶段调用

创建期 getDefaultProps
object getDefaultProps()
创建期 getInitialState
object getInitialState()

在组件挂载前(即:创建期)调用一次,其返回值将做为this.state的初始值

getInitialState()方法会组件类创建的时候调用一次,其返回值会被缓存下来。该方法用于设置props属性的默认值,但仅对于非必须属性。如果父组件没有指定props中的某个值,此返回对象中的相应属性将会合并到this.props

getInitialState()方法会在组件实例创建前调用,这时还不能使用this.props属性,且其返回对象是在所有实例间共享的

创建期 componentWillMount
componentWillMount()

componentWillMount()服务器端和客户端都只调用一次,在初始化渲染执行之前被调用。如果在这个方法内调用setState()方法,render()方法将会收到更新后的state,也就是说这是我做在组件渲染前最后一个修改state的机会

创建期 componentDidMount
componentDidMount()

componentDidMount()会在组件初始化(渲染完成)后立即调用一次,我们一般在这个方法中使用this.getDOMNode()方法访问原始DOM

存在期 componentWillReceiveProps
componentWillReceiveProps(object nextProps)

componentWillReceiveProps()方法会在组件生命周期的存在期调用,当组件感知到props属性改变会,会调用此方法。render()方法将会在其后调用,这时我们可以通过this.setState()来阻止组件的再次渲染。

存在期 shouldComponentUpdate
boolean shouldComponentUpdate(object nextProps, object nextState)

shouldComponentUpdate()方法发生在组件生命周期的存在器,在组件收到新的props或state。在这个方法中,我们可以访问组件的props和state属性,通过这两个属性可以确认组件是否需要更新,如果不需要更新,则返回false,则其后的方法将不会在执行。如:

shouldComponentUpdate: function(nextProps, nextState) {
  return nextProps.id !== this.props.id;
}
存在期 componentWillUpdate
componentWillUpdate(object nextProps, object nextState)

componentWillUpdate()会在收到新的props或state后调用,类似componentWillMount()

存在期 componentDidUpdate
componentDidUpdate(object prevProps, object prevState)

componentDidUpdate()会在组件重新渲染后立即被调用,当我们需要在组件重新渲染后操作DOM则需要使用这个方法

销毁&清理期 componentWillUnmount
componentWillUnmount()

componentWillUnmount()是组件销毁&清理期唯一调用的方法,它会在组件从DOM中移除时被调用,这时我们可以清理一些状态或清理在componentDidMount中创建的DOM元素

样式

可以配置less样式,然后导入到组件,然后利用less的命名空间来约束组件的样式

import React, { Component } from 'react';
import {
	BrowserRouter as Router,
	Route,
	Link
} from 'react-router-dom';
import { matchRoutes, renderRoutes } from 'react-router-config';
import "./root.less";
//新版本的写法 推荐
//class Wscats extends React.Component {
class Wscats extends Component {
	render() {
		return( 
			<div id="root">
				<h1>Root</h1>
					<Link to="/">home</Link> <Link to="/index">index</Link>
					{/* child routes won't render without this */}
					{renderRoutes(this.props.route.routes)}
					{console.log(this)}
			</div>
		)
	}
}
module.exports = Wscats;

root.less

#root {
	h1 {
		font-size: 50px;
		color: green;
	}
}

导出多个组件

平时我们习惯一个单JSX文件导出一个组件,事实上,我们可以一个JSX文件导出多个组件,例如我们可以把一类相似功能的组件放在一起,然后类声明多个组件,然后再导出,我们就可以在使用的时候,导入一个组件,从而使用多个组件的功能

import React from "react";
// 第一个公有组件
class PublicA extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
  }
  render() {
    return (
      <div>
        第一个公有组件
      </div>
    )
  }
}

// 第二个公有组件
class PublicB extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
  }
  render() {
    return (
      <div>
        第二个公有组件
      </div>
    )
  }
}
// 默认导出一个,仅且一个
// export default PublicA
// 导出多个
export {PublicA, PublicB}

注意这里我们用的是export{xxx}而不是export default xxx

下面我们使用的时候就可以引入一个JSX文件,但是同时导入了PublicAPublicB组件了

import React from 'react';
// 导入多个组件
import {PublicA, PublicB} from "../components/public.jsx";
class PageB extends React.Component {
  constructor(props) {
    super(props);
    // 设置 initial state
    this.state = {};

  }
  componentDidMount() {}

  render() {
    return (
      <div>
        <PublicA/>
        <PublicB/>
      </div>
    )
  }
}
export default PageB;

React Hook

useState

在函数组件中,我们没有 this,所以我们不能分配或读取 this.state。我们直接在组件中调用 useState 钩子

import React, { useState } from "react";

function App() {
  // useState(初始值) 返回数组
  // 数组第一个参数是变量,指向状态的当前值
  // 数组第二个参数是函数,用来更新状态,约定是 set 前缀加上状态的变量名
  const [buttonText, setButtonText] = useState(true);
  const [buttonStatus, setButtonStatus] = useState({
    isShow: true
  });

  function changeButtonText() {
    if (buttonText) {
      setButtonText(false);
    } else {
      setButtonText(true);
    }
  }

  function changeButtonStatus() {
    if (buttonStatus.isShow) {
      setButtonStatus({
        isShow: false
      });
    } else {
      setButtonStatus({
        isShow: true
      });
    }
  }

  return <div>
    <button onClick={changeButtonText}>{buttonText ? '1' : '2'}</button>
    <button onClick={changeButtonStatus}>{buttonStatus.isShow ? '显示' : '隐藏'}</button>
  </div>;
}

export default App;

useEffect

代替 componentDidMountcomponentDidUpdate,useEffect 支持接收第二个参数,比如传入 [count] 即当 count 发生改变的时候,触发 useEffect 的回调函数

import React, { useEffect, useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount(1);
  }, [count])

  const [{ isShow, data }, setStatus] = useState({ isShow: true, data: [] });
  useEffect(() => {
    setStatus({ isShow: false, data: ['1', '2'] });
  }, [count])

  return (
    <div className="App">
      {isShow ? count : data}
    </div>
  );
}

export default App;

React-Bootstrap

React-bootstrap官方文档

安装

npm install --save react react-dom
npm install --save react-bootstrap

引入样式

在主页引入CSS样式

<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap-theme.min.css">

使用组件

import Button from 'react-bootstrap/lib/Button';
//或者
import { Button } from 'react-bootstrap';//推荐这种

比如我需要使用按钮的这几个组件

//引入ButtonGroup和Button组件
import {ButtonGroup,Button} from 'react-bootstrap';
//使用ButtonGroup和Button组件
const About = () => (
  <div>
    <h2>关于</h2>
    <ButtonGroup vertical block>
      <Button>Full width button</Button>
      <Button>Full width button</Button>
    </ButtonGroup>
  </div>
)

组件文档

查看其它组件

React JSX语法

JSX

JSX是React的核心组成部分,它使用XML标记的方式去直接声明界面,界面组件之间可以互相嵌套。可以理解为在JS中编写与XML类似的语言,一种定义带属性树结构(DOM结构)的语法,它的目的不是要在浏览器或者引擎中实现,它的目的是通过各种编译器将这些标记编译成标准的JS语言。

虽然你可以完全不使用JSX语法,只使用JS语法,但还是推荐使用JSX,可以定义包含属性的树状结构的语法,类似HTML标签那样的使用,而且更便于代码的阅读。

使用JSX语法后,你必须要引入babel的JSX解析器,把JSX转化成JS语法,这个工作会由babel自动完成。同时引入babel后,你就可以使用新的es6语法,babel会帮你把es6语法转化成es5语法,兼容更多的浏览器。

import React from 'react';
class PageB extends React.Component {
  constructor(props) {
    super(props);
    // 设置 initial state
    this.state = {
    };
  }
  componentDidMount() {
  }
  render() {
    return (
      <div>
      	<p>PageB</p>
      </div>
    )
  }
}
export default PageB;

在这个简单的例子中,看不出来有任何jsx语法的地方,当其中<div><p>PageB</p></div>就是使用到了jsx语法。HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写。

组件

  1. 在react中通常约定组件类的第一个字母必须大写,html标签都是小写。
  2. 组件类只能包含一个顶层标签,否则会报错

详情请参阅 React组件

样式

class属性需要写成 className,for属性需要写成 htmlFor,这是因为classfor是JavaScript的保留字

pageB.jsx

import React from 'react';
//引入外部样式
import './pageB.css';
// React component
class PageB extends React.Component {
  constructor(props) {
    super(props);
    // 设置 initial state
    this.state = {
    };
  }
  componentDidMount() {
  }
  render() {
    return (
      <div>
      	<p className="pageb-p">PageB</p>
      </div>
    )
  }
}

export default PageB;

pageB.css

.pageb-p{
	font-size: 40px;
}

直接在标签上使用style属性时,要写成style={{}}是两个大括号,外层大括号是告知jsx这里是js语法,和真实DOM不同的是,属性值不能是字符串而必须为对象,需要注意的是属性名同样需要驼峰命名法。即margin-top要写成marginTop

比如以下这个render

render() {
  return(
	<div style={{border: '1px solid blue', padding: '20px', marginBottom: '20px'}}>
	{/* 样式通过style={{}}的方法定义,里面的对象的属性值要用驼峰的写法 */}
	<p>{this.state.text}</p>
	<p>{this.props.name}</p>
        <input onChange={this.handleSubmit} />
     	</div>
  )
}

state和props

  1. 组件免不了要与用户互动,React 将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI。
  2. getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。

由于 this.props 和 this.state 都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就不再改变的特性,而 this.state 是会随着用户互动而产生变化的特性。

ES5设置state和props

var HelloMessage = React.createClass({
  getDefaultProps: function() {
    return {
      name: 'Wscats'
    };
  },
  getInitialState: function() {
    return {
      skill: 'PS'
    }
  },
  render: function() {
    return <h1>Hello {this.props.name}{this.state.skill}</h1>;
  }
});
ReactDOM.render(
  <HelloMessage />,
  document.getElementById('example')
);

ES6设置state和props

class HelloMessage extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      skill: 'PS'
    }
  }
  render(){
    return <h1>Hello {this.props.name}{this.state.skill}</h1>
  }
}
HelloMessage.defaultProps = {
   name: 'Wscats'  
}
class Hello extends React.Component {
    this.state = this.getInitialState();
    getInitialState() {
        return {name: "Wscats"};
    } 
    render() {
        return <div>Hello {this.state.name}</div>;
    }
}

获取真实的DOM节点

组件并不是真实的DOM节点,而是存在于内存之中的一种数据结构,叫做虚拟DOM(virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。
有时需要从组件获取真实DOM的节点,这时就要用到ref属性。

import React from 'react';
class PageB extends React.Component {
  constructor(props) {
    super(props);
    // 设置 initial state
    this.state = {
    };
  }
  componentDidMount() {
  	//等到虚拟DOM插入文档以后,即componentDidMount生命周期后才能使用这个属性
  	this.refs.nihao.style.color = "red"
  }
  render() {
    return (
      <div>
      	{/*通过ref来获取真是DOM节点*/}
      	<p >PageC</p>
      	<p ref="nihao">{(function(){
      		return "你好"
      	})()}</p>
      </div>
    )
  }
}
export default PageB;

为了获取真是DOM节点,html元素必须有一个ref属性,然后this.refs.[refName]就会返回这个真实的 DOM节点。需要注意的是,由于this.refs.[refName]属性获取的是真实DOM,所以必须等到虚拟DOM插入文档以后,才能使用这个属性,否则会报错。

求值表达式

要使用JavaScript表达式作为属性值,只需把这个表达式用一对大括号( { } )包起来,不要用引号 ( " " )。
在编写JSX时,在{ }中不能使用语句(if语句、for语句等等),但可以使用求值表达式,这本身与JSX没有多大关系,是JS中的特性,它是会返回值的表达式。我们不能直接使用语句,但可以把语句包裹在函数求值表达式中运用。

条件判断的写法

你没法在JSX中使用if-else语句,因为JSX只是函数调用和对象创建的语法糖。在{ }中使用,是不合法的JS代码,不过可以采用三元操作表达式

可以使用比较运算符“ || ”来书写,如果左边的值为真,则直接返回左边的值,否则返回右边的值,与if的效果相同。

import React from 'react';
import './pageB.css';
class PageB extends React.Component {
  constructor(props) {
    super(props);
    // 设置 initial state
    this.state = {
    	name:""
    };
  }
  componentDidMount() {
  	var self = this;
  	setTimeout(function(){
  		self.setState({
  			name:"我是谁,我在哪里?"
  		})
  	},1000)
  }
  render() {
    return (
      <div>
      	<p>PageB</p>
      	{/*三元操作表达式*/}
      	<p className="pageb-p">1.三元操作</p>
      	<p>{this.state.name?this.state.name:"加载中"}</p>
      	{/*运算符||*/}
      	<p className="pageb-p">2.运算符||</p>
      	<p>{this.state.name||"加载中"}</p>
      </div>
    )
  }
}
export default PageB;

函数表达式

函数的语法可以这样,注意要用自执行,并且要传入this,和return出结果

<p>{(function(){
      	return "Wscats"
})()}</p>
<p>{(function(self){
      	console.log(self.state.name)
      	return self.state.name;
})(this)}</p>
<p>{(function(self){
      	{/*v-if*/}
      	if(self.state.bool){
      		return "假";
      	}else{
      		return "真";
      	}
})(this)}</p>
<p>
      	{/*过滤器*/}
      	{this.ed(this.state.name)}
</p>

过滤器的函数,接受this.state.name数据并处理再输出

//记得在this.state改变this.ed = this.ed.bind(this);
ed(data) {
	//模拟过滤器
	console.log(data)
	return data + "ed"
}

组件的生命周期

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM

详情请参阅 React组件生命周期

注释

JSX里添加注释很容易;它们只是 JS 表达式而已。你只需要在一个标签的子节点内(非最外层)小心地用{}包围要注释的部分。

{/* 一般注释, 用 {} 包围 */}

注意:

  1. 在标签内部的注释需要花括号
  2. 在标签外的的注释不能使用花括号
ReactDOM.render(
    /*注释 */
    <h1>Wscars{/*注释*/}</h1>,
    document.getElementById('example')
);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.