React中文官网
React英文官网
.vue
的文件实现组件化;
.vue
这样的模板文件,所以,也就没有 tempalte,script,style这些概念
mkdir react-demo
cd react-demo
npm init -y
npm install react react-dom -S
npm install webpack webpack-cli webpack-dev-server babel babel-cli babel-core babel-loader babel-preset-react babel-preset-env babel-preset-es2015 -D
(解析jsx和es6语法)
module.exports = {
entry: './main.js',
output: {
path: '/',
filename: 'index.js',
},
module: {
rules: [{
test: /\.js?$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react', 'es2015']
}
}
}]
}
}
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React Apptitle>
head>
<body>
<div id="root">div>
<script src="index.js">script>
body>
html>
main.js
// React 是 React 库的入口
// React组件可以通过扩展 React.Component来定义
import React, { Component } from 'react';
// react-dom 包提供了 DOM 特定的方法,可以在你的应用程序的顶层使用,如果你需要的话,也可以作为 React模型 之外的特殊操作DOM的接口。 但大多数组件应该不需要使用这个模块。
import ReactDom from 'react-dom';
class App extends Component {
render() {
return <h1> Hello, world! </h1>
}
}
// 渲染一个 React 元素到由 container 提供的 DOM 中,并且返回组件的一个 引用
ReactDom.render(
<App />,
document.getElementById('root')
)
package.json
"start": "webpack-dev-server --inline --hot --open --port 8090 --mode development"
React脚手架 (Facebook官方出品)
npm install -g create-react-app
create-react-app my-testproject
cd my-testproject
npm start
or yarn start
import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(
<h1> Hello, world! </h1>,
document.getElementById('root')
)
const element = <h1>Hello, world!</h1>;
这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。
它被称为 JSX, 一种 JavaScript 的语法扩展。 我们推荐在 React 中使用 JSX 来描述用户界面。
注意:只能包含一个根节点
ReactDOM.render(
// 模板只能包含一个根节点
<div>
<h1>hello</h1>
<h2>jack</h2>
</div>,
document.getElementById('root')
);
import React from 'react';
import ReactDom from 'react-dom';
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: '张',
lastName: '三'
};
let num = 2
ReactDom.render(
// 表达式只能放在大括号中,注意和vue一样,同样不支持if else语句
// 如果里面放对象,会报错:Objects are not valid as a React child
<div>
<p>{ 1 + 1}</p>
<p>{ '李' + '宁' }</p>
<h1>hello {formatName(user)}</h1>
<p>{num % 2 === 0 ? '偶数' : '奇数'}</p>
</div>,
document.getElementById('root')
)
{
// 这里是单行注释
}
{
/*
这里是多行注释
这里是多行注释
这里是多行注释
这里是多行注释
*/
}
因为 JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称
import React from 'react';
import ReactDom from 'react-dom';
const user = {
avatar: './avatar.jpg'
}
ReactDom.render(
<div>
<h1>hello</h1>
<h2>jack</h2>
<img src={user.avatar}></img>
{
// class ==> className
// for ==> htmlFor
// tabindex ==> tabIndex
// 自定义属性需要以data-开头,规范
}
<p className="myStyle">类名</p>
<label htmlFor="male">Male</label>
<input type="radio" name="sex" id="male" />
<a href="http://www.google.com/" tabIndex="2">百度</a>
<a href="http://www.google.com/" tabIndex="1">Google</a>
<p data-mytest="123">自定义属性</p>
</div>,
document.getElementById('root')
)
import React from 'react';
import ReactDom from 'react-dom';
let myStyle = {
// React会自动在数值后面加上px
fontSize: 100,
color: '#FF0000'
}
ReactDom.render(
<div>
<h1>hello</h1>
<h2 style={myStyle}>jack</h2>
</div>,
document.getElementById('root')
)
import React from 'react';
import ReactDom from 'react-dom';
// React.createElement 方法的作用,就是使用JS创建内存中的虚拟DOM,生成一些普通的对象
// 这个方法接收至少三个参数:
// 第一个参数: 指定要创建的元素标签类型[string]
// 第二个参数: 指定要创建的元素身上的属性[对象/null]
// 第三个参数: 指定当前创建的元素的子元素
let son = React.createElement('div', {className: 'color-red'}, '我是div里面的盒子')
let father = React.createElement('div', {className: 'color-red'}, '我是div盒子', son)
ReactDom.render(
// 模板只能包含一个根节点
father,
document.getElementById('root')
)
DOM
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u4fSRizw-1629009644288)(./imgs/dom-tree.png)]
浏览器渲染流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eb1s25aU-1629009644293)(./imgs/webkitflow.png)]
什么是Virtual DOM
在React中,render执行的结果得到的并不是真正的DOM节点,结果仅仅是轻量级的JavaScript对象,我们称之为virtual DOM。
虚拟DOM是React的一大亮点,具有batching(批处理)和高效的Diff算法。这让我们可以无需担心性能问题而”毫无顾忌”的随时“刷新”整个页面,由虚拟 DOM来确保只对界面上真正变化的部分进行实际的DOM操作。在实际开发中基本无需关心虚拟DOM是如何运作的,但是理解其运行机制不仅有助于更好的理解React组件的生命周期,而且对于进一步优化 React程序也会有很大帮助
虚拟DOM VS 原生DOM
如果没有 Virtual DOM,简单来说就是直接重置 innerHTML。这样操作,在一个大型列表所有数据都变了的情况下,还算是合理,但是,当只有一行数据发生变化时,它也需要重置整个 innerHTML,这时候显然就造成了大量浪费。
比较innerHTML 和Virtual DOM 的重绘过程如下:
innerHTML: render html string + 重新创建所有 DOM 元素
Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新
DOM 完全不属于Javascript (也不在Javascript 引擎中存在).。Javascript 其实是一个非常独立的引擎,DOM其实是浏览器引出的一组让Javascript操作HTML文档的API而已。在即时编译的时代,调用DOM的开销是很大的。而Virtual DOM的执行完全都在Javascript 引擎中,完全不会有这个开销。
React.js 相对于直接操作原生DOM有很大的性能优势, 很大程度上都要归功于virtual DOM的batching 和diff。batching把所有的DOM操作搜集起来,一次性提交给真实的DOM。diff算法时间复杂度也从标准的的Diff算法的O(n^3)降到了O(n)。
关于React 虚拟DOM的误解
React 从来没有说过 “React 比原生操作 DOM 快”。React给我们的保证是,在不需要手动优化的情况下,它依然可以给我们提供过得去的性能。
框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作。
React掩盖了底层的 DOM 操作,可以用更声明式的方式来描述我们目的,从而让代码更容易维护。
Diff 算法会帮助我们计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行实际 DOM 操作,而非重新渲染整个页面,从而保证了每次操作更新后页面的高效渲染,因此 Virtual DOM 与 diff 是保证 React 性能口碑的幕后推手。
传统Diff算法
传统 diff 算法通过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3),其中 n 是树中节点的总数。O(n^3) 到底有多可怕,这意味着如果要展示1000个节点,就要依次执行上十亿次的比较。这种指数型的性能消耗对于前端渲染场景来说代价太高了!现今的 CPU 每秒钟能执行大约30亿条指令,即便是最高效的实现,也不可能在一秒内计算出差异情况。
React Diff 算法
参考1
参考2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x6E2ApKv-1629009644302)(./imgs/tree-diff.jpg)]
以上两棵树只会对同一层次的节点进行比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0Mxm383-1629009644312)(./imgs/tree-diff2.jpg)]
如果两棵树的根元素类型不同,React会销毁旧树,创建新树
对于类型相同的React DOM 元素,React会对比两者的属性是否相同,只更新不同的属性
React diff 的执行情况:delete A -> create A -> create B -> create C (React 官方建议不要进行 DOM 节点跨层级的操作)
component diff
React 是基于组件构建应用的,对于组件间的比较所采取的策略也是简洁高效。
如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FmYCKJrl-1629009644315)(./imgs/component-diff.jpg)]
D和G为不同类型的组件,会直接删除组件D,重新创建组件G
element diff
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CUvwZejS-1629009644317)(./imgs/insert-F.png)]
如果每个节点都没有唯一的标识,React无法识别每一个节点,那么更新过程会很低效,即,将C更新成F,D更新成C,E更新成D,最后再插入一个E节点,如下图所示。可以看到,React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点E,涉及到的DOM操作非常多。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R8fBUcDO-1629009644319)(./imgs/insert-F-no-diff.png)]
如果给每个节点唯一的标识(key),那么React能够找到正确的位置去插入新的节点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-55cxuPiL-1629009644321)(./imgs/insert-F-diff.png)]
diff演示
组件名称必须以大写字母开头
import React from 'react';
import ReactDom from 'react-dom';
const Hello = (props) => {
return <div>你好,我叫{props.name},我今年{props.age}岁</div>
}
const user = {
name: 'jack',
age: 18
}
const App = () => {
return (
<div>
<Hello name="neil" age="25"/>
<Hello {...user}/>
<Hello />
</div>
)
}
ReactDom.render(
<App />,
document.getElementById('root')
);
function Father(firstName) {
this.firstName = firstName
}
Father.prototype.getFirstName = function () {
console.log(this.firstName);
}
Father.sayHello = function () {
console.log('我是father');
}
function Son(firstName) {
this.firstName = firstName;
}
let father = new Father('李');
father.getFirstName();
Father.sayHello();
Son.prototype = father;
let son = new Son('李2');
son.getFirstName();
class Father {
constructor(firstName) {
this.firstName = firstName
}
getFirstName() {
console.log(this.firstName);
}
static sayHello() {
console.log('我是father');
}
}
class Son extends Father {
constructor(firstName) {
super(firstName);
console.log('son');
}
}
let father = new Father('王');
father.getFirstName();
Father.sayHello();
let son = new Son('王2');
son.getFirstName();
import React, {Component} from 'react';
import ReactDom from 'react-dom';
class Hello extends React.Component {
render() {
return <div>Hello, {this.props.name}</div>
}
}
const props = {
name: 'jack',
age: 18
}
class App extends React.Component {
render () {
return (
<div>
<Hello name="neil" age="25"/>
<Hello {...props}/>
<Hello />
</div>
)
}
}
ReactDom.render(
<App />,
document.getElementById('root')
);
作用:props给组件传递数据,一般用在父子组件之间
注意:props是只读的,无法给props添加或修改属性
props.children:获取组件的内容,比如:
中的组件内容
给类(或者函数)绑定一个defaultProps属性
npm i prop-types -S
import PropTypes from 'prop-types'
Hello.propTypes = {
name: PropTypes.string,
age: PropTypes.number.isRequired
}
作用:用来给组件提供组件内部使用的数据
注意:
import React, {Component} from 'react';
import ReactDom from 'react-dom';
class Hello extends Component {
constructor(props) {
super(props)
this.state = {
country: 'china'
}
}
render() {
return (
<div>
<p>Hello, {this.props.name}</p>
<p>{this.state.country}</p>
</div>
)
}
}
class App extends Component {
render () {
return (
<div>
<Hello name="neil"/>
</div>
)
}
}
ReactDom.render(
<App />,
document.getElementById('root')
);
唯一可以分配 this.state 的地方是构造函数。
不要直接修改 state(状态), 类似于这样this.state.comment = 'Hello'
,用 setState()
代替:this.setState({comment: 'Hello'})
this.setState()方法更新是异步的,此时需要给它传递第二个参数,即一个回调来在更新之后执行
class App extends Component {
constructor(props) {
super(props)
this.state = {
val: 'hello neil'
}
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({
val: 'hello world'
})
// this.setState()是异步的,所以这里的console.log打印出来并不是想要的
console.log(this.state.val);
}
render() {
return (
<button onClick={() => this.handleClick}>{this.state.val}</button>
)
}
}
组件的 state(状态) 可以向下传递,作为其子组件的 props(属性),通常称为一个“从上到下”,或者“单向”的数据流
不同:类允许我们在其中添加本地状态(state)和生命周期钩子
相同:里面props是只读的,无法修改
重点:我们在开发的时候,凡是没有state的组件,就一定要使用函数式组件。为什么呢?因为使用函数的方式创建的组件更易于测试和数据的维护。也就是说,只要我们的组件没有state,我们就要使用函数式组件(也叫无状态组件)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P9XhgpR6-1629009644325)(./imgs/评论列表.png)]
import React, { Component } from 'react'
export default class Home extends Component{
constructor(props) {
super(props)
this.state = {
commentList: [
{ user: '张三', content: '哈哈,沙发' },
{ user: '张三2', content: '哈哈,板凳' },
{ user: '张三3', content: '哈哈,凉席' },
{ user: '张三4', content: '哈哈,砖头' },
{ user: '张三5', content: '哈哈,楼下山炮' }
]
}
}
createComments = () => {
return this.state.commentList.map((item, index) => {
return (
<div key={index}>
<h3>评论人:{item.user}</h3>
<div>评论内容:{item.content}</div>
</div>
)
})
}
render() {
return (
<div>
<h1>评论案例列表</h1>
<div>
{this.createComments()}
</div>
</div>
)
}
}
改造版本:
import React, { Component } from 'react'
const Comment = (props) => {
return (
<div>
<h3>评论人:{props.user}</h3>
<div>评论内容:{props.content}</div>
</div>
)
}
const NumberList = (props) => {
return props.list.map((item, index) => {
return <Comment {...item} key={index}/>
})
}
export default class Home extends Component{
constructor(props) {
super(props)
this.state = {
commentList: [
{ user: '张三', content: '哈哈,沙发' },
{ user: '张三2', content: '哈哈,板凳' },
{ user: '张三3', content: '哈哈,凉席' },
{ user: '张三4', content: '哈哈,砖头' },
{ user: '张三5', content: '哈哈,楼下山炮' }
]
}
}
// createComments = () => {
// return this.state.commentList.map((item, index) => )
// }
render() {
return (
<div>
<h1>评论案例列表</h1>
<div>
<NumberList list={this.state.commentList}></NumberList>
</div>
</div>
)
}
}
React 事件使用驼峰命名
通过 JSX , 你传递一个函数作为事件处理程序
<button onClick={activateLasers}>
Activate Lasers
button>
绑定this(类方法中没有绑定this)
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 这个绑定是必要的,使`this`在回调中起作用
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
class LoggingButton extends React.Component {
// 这个语法确保 `this` 绑定在 handleClick 中。
// 警告:这是 *实验性的* 语法。
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 这个语法确保 `this` 被绑定在 handleClick 中
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
将参数传递给事件处理程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4M9kWK2I-1629009644327)(./imgs/React-Lifecycle.png)]
组件的生命周期可分成三个状态:
Mounting(装载)
当组件实例被创建并将其插入 DOM 时,这些方法将被调用
constructor()
componentWillMount()
render()
componentDidMount()
Updating(更新)
改变 props 或 state 可以触发更新事件。 在重新渲染组件时,这些方法将被调用
componentWillReceiveProps(newProps)
shouldComponentUpdate(newProps, newState)
componentWillUpdate(nextProps, nextState)
render()
componentDidUpdate(prevProps, prevState)
Unmounting(卸载)
当一个组件从 DOM 中删除时,这个方法将被调用:
componentWillUnmount()
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: 0
}
this.setNewNumber = this.setNewNumber.bind(this)
};
setNewNumber() {
this.setState({data: this.state.data + 1})
}
render() {
return (
<div>
<button onClick = {this.setNewNumber}>INCREMENT</button>
{
this.state.data !== 3
? <Content myNumber={this.state.data}></Content>
: <div>Conten组件被销毁了</div>
}
</div>
);
}
}
class Content extends React.Component {
componentWillMount() {
console.log('Component WILL MOUNT!')
}
componentDidMount() {
console.log('Component DID MOUNT!')
}
componentWillReceiveProps(newProps) {
console.log('Component WILL RECIEVE PROPS!')
}
shouldComponentUpdate(newProps, newState) {
return true;
}
componentWillUpdate(nextProps, nextState) {
console.log('Component WILL UPDATE!');
}
componentDidUpdate(prevProps, prevState) {
console.log('Component DID UPDATE!')
}
componentWillUnmount() {
console.log('Component WILL UNMOUNT!')
}
render() {
return (
<div>
<h3>{this.props.myNumber}</h3>
</div>
);
}
}
通讯是单向的,数据必须是由一方传到另一方。在 React 中,父组件可以向子组件通过传 props 的方式,向子组件进行通讯。
class ChildOne extends Component{
render() {
return <p>{this.props.msg}</p>
}
}
class Parent extends Component {
constructor() {
super()
this.state = {
val: 'hello world'
}
}
render() {
return (
<div>
<ChildOne msg={this.state.val}/>
</div>
);
}
}
而子组件向父组件通讯,同样也需要父组件向子组件传递 props 进行通讯,只是父组件传递的,是作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中。
class ChildOne extends Component{
transferMsg = () => {
this.props.getChildMsg('我报名了!')
}
render() {
return <button onClick={this.transferMsg}>点击传值给父组件</button>
}
}
class Parent extends Component {
constructor() {
super()
this.state = {
msg: 'hello world'
}
}
handleMsg = (msg) => {
this.setState({
msg
})
}
render() {
return (
<div>
<div>儿子给我说:{this.state.msg}</div>
<ChildOne getChildMsg={this.handleMsg} />
</div>
);
}
}
对于没有直接关联关系的两个节点,他们唯一的关联点,就是拥有相同的父组件。参考之前介绍的两种关系的通讯方式,如果我们要ChildOne和ChildTwo进行通讯,我们可以先通过 ChildOne 向 Parent 组件进行通讯,再由 Parent 向 ChildTwo 组件进行通讯。注意:这个方法有一个问题,由于 Parent 的 state 发生变化,会触发 Parent 及从属于 Parent 的子组件的生命周期。
class ChildOne extends Component{
transferMsg = () => {
this.props.getChildMsg('我报名了前端!')
}
render() {
return <button onClick={this.transferMsg}>点击传值给父组件</button>
}
}
class ChildTwo extends Component {
render() {
return <p>我兄弟说:{this.props.msg}</p>
}
}
class ChildThree extends Component {
componentDidUpdate() {
console.log('child 3 updated');
}
render() {
return <p>child 3,我和兄弟1和兄弟2之间的通信没有任何关系</p>
}
}
class Parent extends Component {
constructor() {
super()
this.state = {
msg: 'hello world'
}
}
handleMsg = (msg) => {
this.setState({
msg
})
}
render() {
return (
<div>
<div>儿子给我说:{this.state.msg}</div>
<ChildOne getChildMsg={this.handleMsg} />
<ChildTwo msg={this.state.msg} />
<ChildThree/>
</div>
);
}
}
Context
受控表单:设定了value值的input表单就是一个受控表单,此时的表单是不受你控制的,受react控制
import React, {Component} from 'react';
class App extends Component {
render() {
return (
<div>
// 这个value值无法改变,要想改变,只能通过onChange事件
<input type="text" value="Hello!"/>
</div>
);
}
}
export default App;
import React, {Component} from 'react';
class App extends Component {
constructor(props, context) {
super(props, context);
this.state = {
inputVal: 'hello'
};
this.handleChange = this.handleChange.bind(this);
};
handleChange(event) {
this.setState({inputVal: event.target.value});
}
render() {
return (
<div>
<input type="text" value={this.state.inputVal} onChange={this.handleChange} />
</div>
);
}
}
export default App;
不受控表单:value没有值的input是一个不受控组件。用户的任何输入都会反映到输入框中。默认值设置: 和
支持 defaultChecked,而
和
支持 defaultValue(它仅会被渲染一次,在后续的渲染时并不起作用 )。要获取非受控表单的值,需要借助于ref
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
特征 | 不受控制表单(不推荐使用) | 受控表单 |
---|---|---|
一次性检索(例如表单提交) | yes | yes |
及时验证 | no | yes |
有条件的禁用提交按钮 | no | yes |
执行输入格式 | no | yes |
一个数据的几个输入 | no | yes |
动态输入 | no | yes |
注意:尽量少用refs
通过回调函数来实现对dom的引用
定义:ref={(input) => { this.textInput = input; }}
使用:this.textInput.focus()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oOeLoZbj-1629009644330)(./imgs/todo.gif)]
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
list: [
{id: 0, text: 'react'}
]
}
}
handleSubmit = (val) => {
let id = this.state.list.length === 0 ? 0 : this.state.list[this.state.list.length - 1].id + 1
this.setState({
list: this.state.list.concat({ id: id, text: val })
})
}
handleDel = (id) => {
let idx = this.state.list.findIndex(item => item.id === id)
let list = this.state.list
list.splice(idx, 1)
this.setState({
list
})
}
render() {
return (
<div>
<Input onSubmitFn={this.handleSubmit}/>
{
this.state.list.map((item, index) => {
return <List {...item} key={item.id} onDelFn={this.handleDel}/>
})
}
</div>
)
}
}
Input.js
export default class Input extends Component {
constructor(props) {
super(props)
this.state = {
inputVal: ''
}
}
getInputVal = (e) => {
this.setState({
inputVal: e.target.value
})
}
transferVal = (e) => {
let val = this.state.inputVal
if (e.keyCode === 13 && val.trim()) {
this.props.onSubmitFn(val)
this.setState({
inputVal: ''
})
}
}
render() {
return (
<input type="text"
value={this.state.inputVal}
onChange={this.getInputVal}
onKeyUp={this.transferVal}
/>
)
}
}
List.js
export default class List extends Component {
delTodo = (id) => {
this.props.onDelFn(id)
}
render() {
return (
<li>
{this.props.text}
<span style={{ color: 'red', marginLeft: '40px' }} onClick={() => this.delTodo(this.props.id)}>X</span>
</li>
)
}
}
局限:hover等伪类不能够使用
const styleComponent = {
header: {
backgroundColor: "#333333",
color: "#ffffff",
"paddingTop": "15px",
paddingBottom: "15px"
}
}
// jsx中这样使用
<header style={styleComponent.header}></header>
定义一个样式文件,直接引入(通过link标签),然后给相应的元素加上className。它是全局的,会有污染。
也可以直接通过import引入,然后给相应的元素加上className。
const styleComponent = {
header: {
backgroundColor: "#333333",
color: "#ffffff",
"paddingTop": "15px",
paddingBottom: (this.state.isMini) ? "10px" : "15px"
}
}
npm install style-loader css-loader -D
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}
]
}
create-react-app不支持,需要自己手动配置
使用类似下面这样
import cssHeader from (./style.css)
css模块化优点
Css To React
React-router
yarn add react-router-dom
或者npm i react-router-dom -S
路由组件无法接受两个以上子元素
只想匹配某个路由,加exact参数,表示要求路径与location.pathname必须完全匹配
使用 组件来包裹一组。 会遍历自身的子元素(即路由)并对第一个匹配当前路径的元素进行渲染,后面的不会在渲染
HashRouter: http://localhost:8080/#/abc/def
BrowserRouter: http://localhost:8080/abc/def
如果有服务器端的动态支持,建议使用 BrowserRouter
,否则建议使用 HashRouter
。
原因在于,如果是单纯的静态文件,假如路径从 / 切换到 /a 后,此时刷新页面,页面将无法正常访问。
二者的替换方法很简单,我们在引入 react-router-dom 时,如以下:
import { BrowserRouter as Router } from 'react-router-dom'
参数获取:this.props.match.params.xxx
import {Redirect} from 'react-router-dom'
<Redirect to="/404">
// 编程式导航: `this.props.history.push('/xxx/')`
class Product extends Component {
componentDidMount() {
console.log(this.props);
}
jumpTo = () => {
this.props.history.push(`${this.props.match.url}/buy`)
}
render() {
return (
<div>
这里显示商品编号 {this.props.match.params.id} <button onClick={this.jumpTo}>选购</button>
<Route path="/product/:id/buy" render={() => <div>我们这里有XXXXXXXX</div>}></Route>
</div>
)
}
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
productList: [
{id: 11, title: '水果'},
{id: 22, title: '肉类'},
{id: 33, title: '蔬菜'},
]
}
}
render() {
return (
<Router>
<div>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/product/11">蔬菜</Link></li>
<li><Link to="/product/22">水果</Link></li>
<li><Link to="/product/33">肉类</Link></li>
</ul>
<Switch>
<Route path="/" exact render={() => <div>首页</div>}></Route>
<Route path="/product/:id" render={(props) => <Product {...props}/>}></Route>
<Route path="/404" render={() => <div>404</div>}></Route>
<Redirect to="/404"></Redirect>
</Switch>
</div>
</Router>
)
}
}
Ant Design
Material UI
baseURL: https://api.douban.com
第一页 0 = 》(currentPage - 1) * count
第二页 12
第三页 24
接口说明 | URL | 请求方式 | 参数 | 参数说明 |
---|---|---|---|---|
正在热映 | /v2/movie/in_theaters | GET | ?start=xx&count=12 | start表示当前页数据从哪一条开始展示,默认为0,count表示每页显示多少条 |
即将上映 | /v2/movie/coming_soon | GET | ?start=xx&count=12 | - |
top250 | /v2/movie/top250 | GET | ?start=xx&count=12 | - |
详情页 | /v2/movie/subject/ :id | GET | id | 电影id |
Ant Design 在 create-react-app中使用
使用之后可以将样式引入注释掉了,即@import '~antd/dist/antd.css';
,以后直接import { Button } from 'antd';
就能够自动引入组件的js和css
.moviebox{
width: 180px;
text-align: center;
padding: 4px;
margin: 10px 5px;
border: 1px solid #eee;
box-shadow: 0 0 10px #bbb;
transition: all 0.2s;
}
.moviebox:hover{
box-shadow: rgba(0, 0, 0, 0.3) 0px 19px 60px, rgba(0, 0, 0, 0.22) 0px 15px 20px;
}
原生语言程式是为了特定的操作系统而编码,用的也是特定操作系统的开发套件(Platform SDK)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gQrHVn8m-1629009644333)(./imgs/Native-App.png)]
所用技术
优点
缺点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t8KB0wg1-1629009644335)(./imgs/Web-App.png)]
所用技术
优点
缺点
混合语言程式的部份代码会以Web技术编写,如HTML5, CSS和JavaScript。这些程式都是被包裹在原生容器(Native Container)和透过手机上的浏览器引擎来呈现HTML和执行JavaScript,最后可在包装后像原生APP一样上架至应用程式商店
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AqWQzTYS-1629009644337)(./imgs/Hybrid-App.png)]
优点
缺点
Feature | Native | Hybrid | Web |
---|---|---|---|
Skills needed to reach Android and iOS | Skills needed to reach Android and iOS | HTML, CSS, Javascript, Mobile Development Framework | HTML, CSS, Javascript |
Distribution | App Store/Market | App Store/Market | Web |
Development speed | Slow | Moderate | Fast |
Development cost | High | Moderate | Fast |
Maintenance cost | High | Moderate | Low |
Graphical performance | High | Moderate | Moderate |
App performance | High | Moderate | Moderate |
Camera | Yes | Yes | Yes |
Push Notifications | Yes | Yes | No |
Contacts | Yes | Yes | No |
Offline access | Yes | Yes | Yes |
Geolocation | Yes | Yes | Yes |
File upload | Yes | Yes | Yes |
Gyroscope | Yes | Yes | Yes |
Accelerometer | Yes | Yes | Yes |
Swipe Navigation | Yes | Yes | Yes |
Microphone | Yes | Yes | Yes |
Best Used For | Games or consumer-focused apps where performance, graphics, and overall user experience are necessary | Apps that do not have high performance requirements, but need full device access | Apps that do not have high performance requirements, and do not need push notifications or access to contacts |
重力感应
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7GwFCpuX-1629009644339)(./imgs/三种开发类型的原理.png)]
打包DCloud,APICloud,PhoneGap(Cordova),AppCan
npm install ionic cordova -g
ionic start myApp tabs
ionic cordova platform add android
ionic serve
ionic cordova build android
可能需要单独配置Gradle,点击获取方法
可能还会报错,按照报错说明去Android SDK Manager安装相应的包 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HxuqbbRO-1629009644343)(./imgs/error-demo2.jpg)]
HBuilder
jdk-8u161-windows-x64.exe
(32位系统请选择32位的版本)安装到默认路径JAVA_HOME
,值为C:\Program Files\Java\jdk1.8.0_161
Path
,在Path
之后新增%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
CLASSPATH
,值为.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
javac
,如果能出现javac的命令选项,就表示配置成功(略过)
大多数情况下操作系统自带C++环境,不需要手动安装C++环境(略过)
如果运行报错,则需要手动安装visual studio中的C++环境;如下图,但只需要勾选C++就可以了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIk1DlzP-1629009644345)(./imgs/C++.png)]
Git安装完毕后,会自动配置到系统环境变量中;可以通过运行git --version
来检查是否正确安装和配置了Git的环境变量;
Add Python to path
,这样才能自动将Python安装到系统环境变量中;python
,检查是否成功安装了python。离线下载地址1
离线下载地址2
Android SDK Tools安装
installer_r24.4.1-windows.exe
到C:\Android
下面platform-23_r03
(react-native必须依赖这个)复制到platforms
文件夹下,解压到当前文件夹下后,删除压缩文件platform-tools_r27.0.1-windows
,到C:\Android
下面build-tools_r23.0.1-windows.zip(react-native必须依赖这个)
、build-tools_r26-windows.zip(weex必须依赖这个)
,并将解压出来的文件夹,分别改名为版本号23.0.1
、26.0.0
;在安装目录中新建文件夹build-tools
,并将改名为版本号之后的文件夹,放到新创建出来的build-tools
文件夹下extras
文件夹,在extras
文件夹下新建android
文件夹;解压m2responsitory
文件夹,放到新建的extras -> android
文件夹下ANDROID_HOME
,值为C:\Android
,然后再系统环境变量Path
中添加%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-glWOiUki-1629009644347)(./imgs/Android环境.png)]
打包weex程序的时候可能会报的错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-26wuwqEL-1629009644350)(./imgs/error-demo.png)]
安装完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 config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global
运行react-native init AwesomeProject
创建React-Native项目
运行cd AwesomeProject
切换到项目根目录中,运行adb devices
来确保有设备连接到了电脑上
运行下一条命令之前,要确保有设备连接到了电脑上,可以运行adb devices
查看当前接入的设备列表,打包好的文件,放到了下项目文件的android\app\build\outputs\apk
目录下
运行react-native run-android
打包编译安卓项目,并部署到模拟器或开发机中
入坑指南
问题1:开启悬浮框权限;
问题2:Could not get BatchedBridge, make sure your bundle is packaged correctly
解决方案:在终端中,进入到项目的根目录,执行下面这段命令行:
react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/
运行之前,需要确保android/app/src/main/
目录下有assets
文件夹,如果没有,手动创建之~,再运行上面的命令;
问题3:could not connect to development server
解决方案:晃动手机,唤起设置属性窗口,点击“Dev settings”,再点击Debuug server host 出现设置ip地址窗口,填写Ip地址和端口号8081,例如192.168.1.111:8081
npm install -g weex-toolkit
安装Weex 官方提供的 weex-toolkit
脚手架工具到全局环境中weex create project-name
初始化Weex项目weex platform add android
安装android模板,首次安装模板时,等待时间较长,建议fq安装模板android studio
中的安卓模拟器
,或者将启用USB调试的真机
连接到电脑上,运行weex run android
,打包部署weex项目platforms\android\app\build\outputs\apk
目录下View
react-native-router-flux
npm i react-native-router-flux -S
import {Scene, Router} from 'react-native-router-flux';
class App extends React.Component {
render() {
return(
<Router>
<Scene key="root">
<Scene key="login" component={Login} title="Login"/>
<Scene key="register" component={Register} title="Register"/>
<Scene key="home" component={Home}/>
</Scene>
</Router>
)
}
}
react-native-swiper
Redux 是 JavaScript (不是React,其他的像Angular也可以使用,甚至单纯的JavaScript也阔以使用)状态容器,提供可预测化的状态管理
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vbv1zYCX-1629009644352)(./imgs/Redux.png)]
Store
store是Redux的实例对象,专门用来存放应用的状态,它里面保存有应用的state,action,reducer。注意:应用程序只能有唯一一个store
store有以下职责:
import { createStore, combineReducers } from 'redux'
// 创建一个Redux实例,让它去管理应用中的state
// 1. createStore 函数可以传三个参数,分别是reducer (Function);初始时的state和enhancer (Function),enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator
// 2. combineReducers()函数会将多个不同的reducer合并成一个大的reducer函数,合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。
const store = createStore(combineReducers({userReducer}));
// store.subscribe()方法可以监听store的更新
store.subscribe(() => {
console.log("Store updated!", store.getState());
});
State
State 是一个普通对象,用来保存应用的数据。例如:
const initialState = {
result: 1,
lastValues: [],
username: "Max"
};
state对象中的数据不能随意修改,要想更改state中的数据,需要用到发起一个action,这个action是一个对象,描述了你要干什么事情
Action
action
Action 就是一个普通 JavaScript 对象,用来描述发生了什么事情。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作,type 会被定义成字符串常量。
{
type: "SET_AGE",
payload: 30
}
// 提交一个action
store.dispatch({
type: "SET_AGE",
payload: 30
});
但它并没有去修改state,为了去做action描述的事情,我们需要用reducer将action和state联系起来。
* action创建函数
action创建函数是用来创建action的函数,就是专门用来生成action对象的函数,例如:
```js
function addTodo(text) {
return {
type: ADD_TODO,
payload: text
}
}
Reducer
reducer是一些纯函数,它接收两个参数:state和action,返回值是一个新的state。
注意:
const userReducer = (state = {
name: "Max",
age: 27
}, action) => {
switch (action.type) {
case "SET_NAME":
state = {
...state,
name: action.payload
};
break;
case "SET_AGE":
state = {
...state,
age: action.payload
};
break;
}
return state;
};
npm i react-redux -S
import {Provider} from 'react-redux'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
import {connect} from "react-redux";
const mapStateToProps = (state) => {
return {
// state.userReducer这个userReducer,必须和上面const store = createStore(combineReducers({userReducer}));中的userReducer名字对应
user: state.userReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
setName: (name) => {
dispatch({
type: "SET_NAME",
payload: name
});
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
// 接下来,可以通过this.props.user.name获取state中的name值
// 通过this.props.setName('xxx')触发一个reducer
系起来。
* action创建函数
action创建函数是用来创建action的函数,就是专门用来生成action对象的函数,例如:
```js
function addTodo(text) {
return {
type: ADD_TODO,
payload: text
}
}
```
Reducer
reducer是一些纯函数,它接收两个参数:state和action,返回值是一个新的state。
注意:
const userReducer = (state = {
name: "Max",
age: 27
}, action) => {
switch (action.type) {
case "SET_NAME":
state = {
...state,
name: action.payload
};
break;
case "SET_AGE":
state = {
...state,
age: action.payload
};
break;
}
return state;
};
npm i react-redux -S
import {Provider} from 'react-redux'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
import {connect} from "react-redux";
const mapStateToProps = (state) => {
return {
// state.userReducer这个userReducer,必须和上面const store = createStore(combineReducers({userReducer}));中的userReducer名字对应
user: state.userReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
setName: (name) => {
dispatch({
type: "SET_NAME",
payload: name
});
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
// 接下来,可以通过this.props.user.name获取state中的name值
// 通过this.props.setName('xxx')触发一个reducer