<body>
<div id="root">div>
<script src="./react.development.js">script>
<script src="./react-dom.development.js">script>
<script src="./babel.min.js">script>
<script type="text/babel">
// 创建虚拟DOM
const VDOM = <div>Hello World</div>
// 渲染虚拟DOM
ReactDOM.render(VDOM, document.getElementById('root'));
script>
body>
<script type="text/babel">
// 创建虚拟DOM
const VDOM = <div>Hello World</div>
// 渲染虚拟DOM
ReactDOM.render(VDOM, document.getElementById('root'));
script>
<script type="text/javascript">
// 创建虚拟DOM
const VDOM = React.createElement('div', {id: 'title'}, 'Hello World')
// 渲染虚拟DOM
ReactDOM.render(VDOM, document.getElementById('root'));
script>
const obj = {a: {b: 1}};
const {a} = obj; // 传统结构赋值
const {a: {b}} = obj; // 连续结构赋值
const {a: {b: newName}} = obj; // 连续结构赋值 + 重命名
<script type="text/babel">
// 创建函数式组件
function MyComponent() {
console.log(this); // 因为bable编译开启了严格模式,此处的this是undefined
return <h2>我是函数式组件</h2>
}
// 渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById('root'));
script>
<script type="text/babel">
// 创建类式组件
class MyComponent extends React.Component {
// render方法是放在MyComponent的原型对象上的,供实例使用
render() {
// render中的this是MyComponent的实例对象
console.log(this);
return <div>这是一个类组件</div>
}
}
// 渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById('root'));
script>
class Weather extends React.Component {
state = {
isHot: false
}
constructor(props) {
super(props);
this.demo = this.demo.bind(this); // 修改demo方法中的this指向
}
render() {
const {isHot} = this.state;
// 此处changeWeather方法只是赋值给了onClick,并未调用
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>;
}
// 箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象)
changeWeather = () => {
// 内置API修改state
this.setState({isHot: !this.state.isHot});
}
// 事件中调用自定义函数时,this为undefined。
// 原因1:demo方法并非由Weather实例对象调用
// 原因2:类中的方法,局部开启了严格模式,导致this为undefined
demo() {
console.log(this);
}
}
class Person extends React.Component {
// 限定标签属性类型、是否必传
static propTypes = {
// PropTypes需要引入prop-types.js文件
name: PropTypes.string.isRequired, // name必传,且为字符串
age: PropTypes.number, // 限制为数值
sex: PropTypes.string, // 限制为字符串
speak: PropTypes.func // 限制为函数
}
// 指定标签属性默认值
static defaultProps = {
sex: 'man',
age: 18
}
// react基本不用构造函数
constructor(props) {
super(props);
// 是否接受props和是否给super传递props,取决于constructor中石否用到this.props。不传则取不到
console.log(this.props);
}
render() {
const {name, age, sex} = this.state;
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
);
}
}
const data = {
name: '张三',
age: 18,
sex: '男'
}
function speak() {
console.log('speak');
}
// 渲染组件到页面
ReactDOM.render(<Person name={data.name} age={data.age} sex={data.sex} speak={speak}/>, document.getElementById('root1'));
// 此处的{...data}并非构造字面量对象时使用的展开语法。{}表示嵌入js表达式,jsx标签中可以直接使用...obj展开一个对象
ReactDOM.render(<Person {...data}/>, document.getElementById('root2'));
function Person(props) {
const {name, age, sex} = props;
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{sex}</li>
</ul>
);
}
// 限定标签属性类型、是否必传
Person.propTypes = {
name: PropTypes.string.isRequired, // name必传,且为字符串
age: PropTypes.number, // 限制为数值
sex: PropTypes.string, // 限制为字符串
speak: PropTypes.func // 限制为函数
}
// 指定标签属性默认值
Person.defaultProps = {
sex: 'man',
age: 18
}
const data = {
name: '张三',
age: 18,
sex: '男'
}
function speak() {
console.log('speak');
}
// 渲染组件到页面
ReactDOM.render(<Person name={data.name} age={data.age} sex={data.sex} speak={speak}/>, document.getElementById('root1'));
ReactDOM.render(<Person {...data}/>, document.getElementById('root2'));
class Demo extends React.Component {
render() {
return (
<div>
<div>
<input ref="input" type="text"/>
<button onClick={this.getValue}>按钮</button>
</div>
</div>
);
}
getValue = () => {
const { input } = this.refs;
console.log(input.value);
}
}
// 内联回调函数 和 绑定回调函数 实际使用中并没有什么影响,推荐使用内联回调
class Demo extends React.Component {
render() {
return (
<div>
<div>
{ /* 内联回调函数:在state更新的时候,内联回调会被调用两次,第一次c为null,第二次c为节点 */ }
<input ref={element => this.input = element} type="text"/>
<button onClick={this.getValue}>按钮</button>
</div>
<div>
{ /* 绑定回调函数:在state更新的时候,绑定的函数不会被再次调用 */ }
<input ref={this.getInput} type="text"/>
<button onClick={this.getValue}>按钮</button>
</div>
</div>
);
}
getInput = (element) => {
this.input = element;
}
getValue = () => {
const { input } = this;
console.log(input.value);
}
}
class Demo extends React.Component {
myRef = React.createRef();
render() {
return (
<div>
<div>
<input ref={this.myRef} type="text"/>
<button onClick={this.getValue}>按钮</button>
</div>
</div>
);
}
getValue = () => {
console.log(this.myRef.current.value);
}
}
// 在提交的时候才取input中的值
class Demo extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
console.log(`用户名:${this.userName.value}---密码:${this.password.value}`)
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input type="text" ref={c => this.userName = c} />
密码:<input type="password" ref={c => this.password = c} />
<button>提交</button>
</form>
);
}
}
// 页面输入类的DOM,随着输入将数据维护到state中。用的时候从状态中取
class Demo extends React.Component {
state = {
userName: '',
password: ''
}
handleSubmit = (e) => {
e.preventDefault();
const {userName, password} = this.state;
console.log(`用户名:${userName}---密码:${password}`)
};
saveUserName = (e) => {
this.setState({ userName: e.target.value });
};
savePassword = (e) => {
this.setState({ password: e.target.value });
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input type="text" onChange={this.saveUserName}/>
密码:<input type="password" onChange={this.savePassword}/>
<button>提交</button>
</form>
);
}
}
class Demo extends React.Component {
state = {
userName: '',
password: ''
}
// 高阶函数(返回值是一个函数)
saveFormData1 = (type) => {
return event => {
this.setState({[type]: event.target.value});
}
};
saveFormData2(type, event) {
this.setState({[type]: event.target.value});
}
render() {
return (
<div>
{ /*用函数柯里化实现*/ }
<form>
用户名:<input type="text" onChange={this.saveFormData1('userName')}/>
密码:<input type="password" onChange={this.saveFormData1('password')}/>
<button>提交</button>
</form>
{ /*不用函数柯里化实现*/ }
<form onSubmit={this.handleSubmit}>
用户名:<input type="text" onChange={event => this.saveFormData2('userName', event)}/>
密码:<input type="password" onChange={event => this.saveFormData2('password', event)}/>
<button>提交</button>
</form>
</div>
);
}
}
// 此方法调用,state的值在任何时候都取决于props。state将无法进行修改
// props为组件传入的参数,state为组件定义的state
static getDerivedStateFromProps(props, state) {
console.log('new-----getDerivedStateFormProps');
return props;
}
ReactDOM.render(<NewReact count={123}/>, document.getElementById('root'))
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log(prevProps, prevState)
return '123';
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(prevProps, prevState, snapshot); // snapshot = '123'
}
key是虚拟DOM对象的一个标识
当状态中的数据发生变化,React会根据新数据生成新的虚拟DOM。随后React将进行新的虚拟DOM与旧的虚拟DOM的Diffing算法比较。规则如下
index作为key可能引发的问题:
开发中如何选择key?:
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="red" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React Apptitle>
head>
<body>
<noscript>You need to enable JavaScript to run this app.noscript>
<div id="root">div>
body>
ReactDOM.render(
// 用于检测内部包裹的所有子组件中,react使用是否合理(例如字符串形式的ref)
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// 用于记录页面性能,需要在reportWebVitals.js文件中进行相应配置
reportWebVitals();
import axios from 'axios';
axios.get('http://localhost:5000/students').then(
res => {},
err => {}
)
// package.json
{
"proxy": "http://localhost:5000"
}
// 使用注意
// 浏览器不再直接给5000端口发送请求,而是像3000端口请求
// 3000服务器收到地址,先在当前端口找是否有匹配的地址,如果没有再向5000端口进行请求
axios.get('http://localhost:3000/students').then();
// 1.在src目录下新增setupProxy.js文件
// 2.配置setupProxy.js文件
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
proxy('/api', { // 匹配到'/api'前缀的请求,就会触发代理配置
target: 'http://localhost:5000', // 跨域目标服务器
// changeOrigin 控制服务器收到的请求头中HOST的值
// true => 与服务区地址一致:http://localhost:5000
// false => 真实的当前地址:http://localhost:3000
changeOrigin: true,
pathRewrite: {'^/api': ''} // 重写请求路径,将'/api'替换为''
}),
proxy('/master', { // 可以传多个参数
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/master': ''}
})
)
}
// 关注分离
fetch(`https://api.github.com/search/users?q=${this.state.keyword}`)
.then( // 联系服务器成功与否
response => {
console.log('联系服务器成功', response);
return response.json();
}
)
.then(data => console.log(data)) // 获取数据
.catch(err => console.log(err))
// 代码优化
getData = async () => {
try {
const response = await fetch(`https://api.github.com/search/users?q=${this.state.keyword}`);
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
}
// 父组件
export default class App extends Component {
state = {
list: [],
isFirst: true,
isLoading: false,
err: ''
}
render() {
return (
<div className="container">
{ /* 传递函数 */ }
<Search updateState={this.updateState}/>
{ /* 批量传参 */ }
<List {...this.state}/>
</div>
)
}
updateState = (state) => {
this.setState(state)
}
}
// Search子组件
export default class Search extends Component {
getData = () => {
const {updateState} = this.props;
updateState({isFirst: false, isLoading: true, err: ''})
axios.get(`https://api.github.com/search/users?q=${this.state.keyword}`).then(
res => {
updateState({isLoading: false, list: res.data.items});
},
err => {
updateState({isLoading: false, err: err.message})
}
)
}
}
// List 子组件
export default class List extends Component {
render() {
const { list, isFirst, isLoading, err } = this.props;
return (
<div className="row">
{
isFirst ? <h2>第一次进入</h2> :
isLoading ? <h2>Loading......</h2> :
err ? <h2>err</h2> :
list.map(item => {
return (
<div className="card" key={item.id}>
<a href={item.html_url} target="_blank" rel="noreferrer">
<img alt="header_pic" src={item.avatar_url} style={{ width: '100px' }} />
</a>
<p className="card-text">{item.login}</p>
</div>
)
})
}
</div>
)
}
}
// 订阅消息
import PubSub from 'pubsub-js';
export default class List extends Component {
state = {
list: [],
isFirst: true,
isLoading: false,
err: ''
}
componentDidMount() {
// 订阅
this.token = PubSub.subscribe('updateList', (msg, data) => this.setState(data))
}
componentWillUnmount() {
// 取消订阅
PubSub.unsubscribe(this.token);
}
}
// 发布消息
export default class Search extends Component {
getData = () => {
// 发布
PubSub.publish('updateList', {isFirst: false, isLoading: true, err: ''});
axios.get(`https://api.github.com/search/users?q=${this.state.keyword}`).then(
res => {
PubSub.publish('updateList', {isLoading: false, list: res.data.items});
},
err => {
PubSub.publish('updateList', {isLoading: false, err: err.message});
}
)
}
}
<Link to="/home">HomeLink>
<NavLink activeClassName="active" to="/about">NavLink>
<Route path="/about" component={ About }/>
<Routes>
<Route path="/home" element={ /> }>Route>
Routes>
props接收标签传递的属性参数,同时接收标签体内容在props.children中。
使用children时可以直接赋值给标签属性children,即展示标签体内容
// 封装
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="active-class" className="list-group-item" {...this.props} />
// 等同于
<NavLink activeClassName="active-class" className="list-group-item" to={this.props.to} children={this.props.children} />
// 等同于
<NavLink activeClassName="active-class" className="list-group-item" to={this.props.to}>{this.props.children}</NavLink>
)
}
}
// 使用
<MyNavLink to="/about">About</MyNavLink>
BrowserRouter下使用多层级路由,会导致使用相对路径的文件丢失(找不到文件,默认返回index.html)。
<link rel="stylesheet" href="./css/bootstrap.css">
<Route path="/api/home" component={ Home }/>
解决方法:
<link rel="stylesheet" href="/css/bootstrap.css">
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
ReactDOM.render(<HashRouter><App /></HashRouter>, document.getElementById('root'));
<MyNavLink to="/home/a/b">homeMyNavLink>
<MyNavLink to="/a/home/b">homeMyNavLink>
<Switch>
<Route path="/home" component={ Home }/>
Switch>
<MyNavLink to="/home/a/b">homeMyNavLink>
<Switch>
<Route exact path="/home" component={ Home }/>
Switch>
<Switch>
<Route path="/about" component={ About }/>
<Route exact path="/home" component={ Home }/>
<Redirect push from="/test" to="/home">Redirect>
<Redirect exact to="/about">Redirect>
Switch>
<Switch>
<Route path="/about" component={ About }/>
<Route path="/home" component={ Home }/>
<Redirect to="/about">Redirect>
Switch>
<Switch>
<Route path="/home/news" component={ News } />
<Route path="/home/message" component={ Message } />
<Redirect to="/home/news">Redirect>
Switch>
import qs from 'querystring'; // 转换查询字符串的库,react自带。qs.parse <=> qs.stringify
const search = this.props.location.search;
const {id, title} = qs.parse(search.slice(1));
<Route push path="/home/message/detail" component={ Detail } />
<Route replace path="/home/message/detail" component={ Detail } />
// 携带params参数
this.props.history.push(`/home/message/detail/${id}/${title}`);
// 携带search参数
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`);
// 携带state参数
this.props.history.push(`/home/message/detail`, {id, title});
import {withRouter} from 'react-router-dom';
class Header extends Component {
forward = () => {
this.props.history.goForward();
}
render() {
return (
<div>
<h2>React Router Demo</h2>
<button onClick={this.forward}>forward</button>
</div>
)
}
}
export default withRouter(Header);
src下建立:
store.js:
import { createStore, applyMiddleware } from 'redux';
import { countReducer } from './counter_reducer';
import thunk from 'redux-thunk';
export default createStore(countReducer, applyMiddleware(thunk))
count_reducer.js:
const countInit = 0;
export default function countReducer(preState = countInit, action) {
const { type, data } = action;
switch(type) {
case 'increment':
return preState + data;
case 'decrement':
return preState - data;
default:
return preState;
}
}
count_action.js
// 同步action
export function createIncrementAction(data) {
return {type: 'increment', data};
}
// 异步action
export function createIncrementAsyncAction(data, time) {
// store.dispatch调用此方法时,会传入dispatch方法
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time);
}
}
在index.js中监测store中状态的改变,一旦发生改变重新渲染
import store from './store';
store.subscribe(ReactDOM.render(<App />, document.getElementById('root')));
// 容器组件完整写法
import Count from '../../components/Count'; // UI组件
import { connect } from 'react-redux'; // 连接UI组件与redux,并返回一个容器组件
import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from '../../redux/count/count_action'
// 给UI组件传递props属性
const mapStateToProps = (state) => {
return {count: state};
}
// 给UI组件传递方法
const mapDispatchToProps = (dispatch) => {
return {
increment: data => dispatch(createIncrementAction(data)),
decrement: data => dispatch(createDecrementAction(data)),
asyncIncrement: data => dispatch(createIncrementAsyncAction(data))
}
}
// 连接UI组件与redux,并返回一个容器组件
export default connect(mapStateToProps, mapDispatchToProps)(Count);
// 容器组件简写
export default connect(
state => ({count: state}),
{
increment: createIncrementAction,
decrement: createDecrementAction,
asyncIncrement: createIncrementAsyncAction
}
)(Count);
import ReactDOM from 'react-dom'
import App from './App';
import store from './redux/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>
, document.getElementById('root'));
import { createStore, combineReducers } from 'redux';
import countReducer from './reducers/count';
import personReducer from './reducers/person';
const reducers = combineReducers({
count: countReducer,
persons: personReducer
});
export default createStore(reducers);
export default connect(
state => ({count: state.count, persons: state.persons}),
{increment: createIncrementAction}
)(Count)
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import countReducer from './reducers/count'
import personReducer from './reducers/person';
import thunk from 'redux-thunk';
const allReducer = combineReducers({
count: countReducer,
persons: personReducer
})
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
render() {
console.log('render')
}
hanldeClick() {
this.setState({ name: 'jack' })
this.setState({ age: 12 })
}
hanldeClick() {
// 连续调用三次对象式setState,结果是count只+1。
// 因为setState异步调用,每次调用完,this.state.count状态并未更新,三次调用取到的count值是一样的
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
// 连续调用三次函数式setState,结果是count+3
// 多次调用函数式setState的情况,React会保证调用每次函数,state都已经合并了之前的状态修改结果。
this.setState(state => ({count: state.count + 1}));
this.setState(state => ({count: state.count + 1}));
this.setState(state => ({count: state.count + 1}));
}
React.useState()
// state更新,Hooks函数会重新调用。React在第一次调用useState后,会对创建的state进行缓存,后续执行不会覆盖原有结果
export default function Hooks () {
// 数组结构赋值,第一个为定义的state属性,第二个为修改属性的方法
const [count, setCount] = React.useState(0);
function add() {
// 同setState方法
setCount(count + 1);
setCount(count => ++count);
}
return (
<div>
<div>Count: { count }</div>
<button onClick={add}>+1</button>
</div>
)
}
React.useEffect()
export default function Demo () {
const [count, setCount] = React.useState(0);
// 参数一(回调函数):初始化会执行一次,后续更新state是否调用,取决于参数二
// 参数二(数组):不传第二参数时,相当于检测所有state,任何state更新,参数一回调都会调用。传空数组,相当于不检测任何state。数组中指定检测count,则在初始化加载和count更新时,调用参数一的回调函数。
React.useEffect(() => {
const timer = setInterval(() => {
setCount(count => ++count);
}, 1000);
// 参数一的回调函数返回值,相当于componentWillUnmount钩子函数
return () => {
clearInterval(timer);
}
}, [count])
function add() {
setCount(count => ++count);
}
function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}
return (
<div>
<div>Count: { count }</div>
<button onClick={add}>+1</button>
<button onClick={unmount}>卸载</button>
</div>
)
}
React.useRef()
export default function Demo () {
const input = React.useRef();
function getRef() {
console.log(input.current.value);
}
return (
<div>
<input type="text" ref={input} />
<button onClick={getRef}>getRef</button>
</div>
)
}
import React, { Component, Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<div>demo</div>
</Fragment>
)
}
}
import React, { Component } from 'react';
// 创建Context容器对象。祖组件与后代组件公用
const MyContext = React.createContext();
// 提取Provider,方便祖组件包裹后代组件
const {Provider} = MyContext;
export default class A extends Component {
state = {name: '张三'}
render() {
return (
<div>
<h3>A组件</h3>
<h4>state: { this.state.name }</h4>
<hr />
{ /*
也可以用
value中的传值,后代组件都可以使用。value可以传值,也可以传对象
*/ }
<Provider value={ this.state.name }>
<B />
</Provider>
</div>
)
}
}
// 子组件传值一般不用Context,因为provider更便捷
// 取值方式一:
class B extends Component {
// 使用context中的数据之前,需要进行申明
// 声明后,this中的context才会有值
static contextType = MyContext;
render() {
return (
<div>
<h3>B组件</h3>
<h4>state:{this.context}</h4>
<hr />
<C />
</div>
)
}
}
// 取值方式二:
// 函数组件:只能通过方式二取值
class C extends Component {
render() {
return (
<div>
<h3>C组件</h3>
<MyContext.Consumer>
{ value => (<h4>state:{value}</h4>) }
</MyContext.Consumer>
<hr />
</div>
)
}
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.name !== this.state.name
}
shouldComponentUpdate(nextProps, nextState) {
return nextProps.name !== this.props.name;
}
}
import React, { PureComponent } from 'react'
export default class Parent extends PureComponent {}
export default class A extends Component {
render() {
return (
<div>
<h3>A组件</h3>
<B><C /></B>
</div>
)
}
}
class B extends Component {
render() {
return (
<div>
<h3>B组件</h3>
{this.props.children}
</div>
)
}
}
export default class A extends Component {
render() {
return (
<div>
<h3>A组件</h3>
<B render={name => <C name={name}/>}/>
</div>
)
}
}
class B extends Component {
render() {
return (
<div>
<h3>B组件</h3>
{this.props.render('张三')}
</div>
)
}
}
state = {
hasError: ''
}
// 生命周期函数,一旦后代组件包错,就会出发
static getDerivedStateFromError(error) {
return {hasError: error}
}
// 统计页面的错误,发送请求的后台
componentDidCatch(error, info) {
console.log(error, info)
}
render() {
return (
<div>{this.state.hasError ? '页面出错' : <Child />}</div>
)
}