1.入门
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello</title>
</head>
<body>
<div id="test"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script type="text/babel">
// 1.入门
{
// (1).创建虚拟DOM
// const VDOM = <h1>Hello</h1>
// (2).渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉
// ReactDOM.render(VDOM,document.getElementById("test"));
}
// 2.JSX语法
{
// const myId = "title";
// const myData = "Hello";
// (1).创建虚拟DOM
// const VDOM = (
// <h1 id={myId.toLocaleUpperCase()}>
// <span className="active" style={{ fontSize: '50px' }}>{myData}</span>
// </h1>
// )
// (2).渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉
// ReactDOM.render(VDOM, document.getElementById("test"));
}
// 3.两种创建虚拟DOM的方式
{
// (1).JSX创建虚拟DOM
// const VDOM = (
// <h1 id="title">
// <span className="active" style={{ fontSize: '50px' }}>Hello</span>
// </h1>
// )
// (2).JS创建虚拟DOM
// const vDOM = React.createElement('h1',{id:"title"}, React.createElement('span', {}, 'Hello'));
// 第一种方式会被babel转为第二种方式(devtools元素面板可验证)
// 所以第一种方式是第二种的语法糖,由react提供
}
// 4.两种组件
{
// (1).函数式组件:适于简单组件
// function Welcome(props) {
// return <h1>Hello, {props.name}</h1>;
// }
// ReactDOM.render(<Welcome name="A"/>, document.getElementById("test"));
// (2). 类式组件:适于复杂组件
// class Welcome extends React.Component {
// render() {
// return <h1>Hello, {this.props.name}</h1>
// }
// }
// ReactDOM.render(<Welcome name="B"/>, document.getElementById("test"));
}
// 5.组件嵌套
{
// function GetLi(props) {
// return <li>{props.value}</li>
// }
// class GetUl extends React.Component {
// render() {
// return (
// <ul>
// {
// this.props.arr.map((item, index) =>
// <GetLi value={item} key={index} />
// )
// }
// </ul>
// )
// }
// }
// ReactDOM.render(<GetUl arr={[1, 2, 3, 4]} />, document.getElementById("test"));
}
</script>
</body>
</html>
2. 三大属性
(1).state
{
class MyComp extends React.Component {
constructor(props) {
super(props);
this.state = { status: false };
//这里的this必然是实例对象
this.changeStatus = this.changeStatus.bind(this);
//上面这句话,右边的changeStatus是原型对象上的,左边的changeStatus是实例对象上的
}
//类中定义的方法都在原型对象上
render() {
console.log(this);//通过Mycomp的实例调用render方法时,这里的this是该实例对象
return <h1 onClick={this.changeStatus}>状态:{this.state.status ? '正常' : '异常'}</h1>
}
//类中定义的方法都在原型对象上
changeStatus() {
console.log(this);//通过Mycomp的实例调用render方法时,这里的this是该实例对象
//但是当h1标签被点击,从而调用changeStatus方法时,实际调的是它的一个克隆体(全局作用域内)
//此时this理应为window对象,但是会开启局部严格模式,则this最终为undefined
//拿不到正确的this就无法修改status,那咋办?
//有些方法(如call、apply、bind)是可以修改this的
//比如 this.changeStatus.bind(this)会返回一个新的方法并为新方法制定了this
//那么我们希望onClick的回调,是这个新方法
//目前onClick的回调,是原型对象的老方法
//而之所以是原型对象上的方法,是因为实例对象上没有找到名为changeStatus的方法,如果找到了,那么优先用这个
//那么思路就清晰了:给实例对象加个名为changeStatus的属性,属性值是this.changeStatus.bind(this)的返回值
//加属性要在构造器里加
// this.state.status = true;
// console.log(this.state.status);//true
//直接更改,没有“响应式”的效果
//在vue中也是这样,state放在data里,如果只改state的属性,则不触发视图的更改(错×,vue深度监听了对象)
// this.state = { status: !this.state.status };
// console.log(this.state);
//在react中,上面这么写也不行,得借助框架提供的API
this.setState({ status: !this.state.status });
// 如果只是修改state的部分属性,则不会影响其他的属性,这个只是合并并不是覆盖。上面我自己的写法是覆盖
}
}
ReactDOM.render(<MyComp />, document.getElementById("test"));
}
state 简化写法:
class MyComp extends React.Component {
state = { isHot: false };
render() {
return <h1 onClick={this.changeState}>今天天气{this.state.isHot ? '炎热' : '凉爽'}</h1>
}
//箭头函数没有自己的this,会向外找
changeState = () => {
this.setState({ isHot: !this.state.isHot });//异步操作
}
}
ReactDOM.render(<MyComp />, document.getElementById("test"));
/**
* React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。
* 大部分开发中用到的都是React封装的事件,比如onChange、onClick等,setState都是异步处理的。
**/
React调用setState方法时,会有同步与异步之分。
如果是同步更新,每一个setState都会执行,每次执行会调用一次render(多次渲染)
如果是异步更新,多个setState会仅执行最后一个,这样仅调用一次render(一次渲染)
class MyComp extends React.Component {
state = { isHot: false, cnt: 0 };
render() {
return <h1 onClick={this.changeState}>今天天气{this.state.isHot ? '炎热' : '凉爽'},数量:{this.state.cnt}</h1>
}
changeState = () => {
this.setState({ cnt: this.state.cnt + 1 });
this.setState({ cnt: this.state.cnt + 3 });
this.setState({ cnt: this.state.cnt + 5 });
}
}
ReactDOM.render(<MyComp />, document.getElementById("test"));
这里 cnt 最终为5,也就是仅执行了最后一个setState。
自定义方法的传参
class MyComp extends React.Component {
state = { isHot: false, cnt: 0, num: 5 };
render() {
// return <h1 onClick={(e) => this.changeState(this.state.num, e)}>今天天气{this.state.isHot ? '炎热' : '凉爽'},数量:{this.state.cnt}</h1>
return <h1 onClick={this.changeState.bind(this, this.state.num)}>今天天气{this.state.isHot ? '炎热' : '凉爽'},数量:{this.state.cnt}</h1>
}
changeState = (n, e) => {
this.setState({ cnt: this.state.cnt + n });
console.log(e.target);
}
}
ReactDOM.render(<MyComp />, document.getElementById("test"));
有①箭头函数②bind两种方式
第二种方式e也能读取到
(2).props
props是只读的(read-only)
给组件传值
class Person extends React.Component {
render() {
return (
<ul>
<li>name: {this.props.name}</li>
<li>age: {this.props.age}</li>
<li>gender: {this.props.gender}</li>
</ul>
);
}
}
ReactDOM.render(<Person name="Joy" age="25" gender="male"/>, document.getElementById("test"));
给组件传一个对象
class Person extends React.Component {
render() {
return (
<ul>
<li>name: {this.props.name}</li>
<li>age: {this.props.age}</li>
<li>gender: {this.props.gender}</li>
</ul>
);
}
}
ReactDOM.render(<Person name="Joy" age="25" gender="male"/>, document.getElementById("test"));
const p = { name: "Mike", age: 28, gender: "male" };
ReactDOM.render(<Person {...p}/>, document.getElementById("test2"));
原本在JS中,剩余运算符不能用于展开一个对象,这里能这么写,是因为react+babel允许在自定义组件中写这种语法
纯JS中这么写的话,只能是复制一个对象:
const p1 = {name:"张三",age:"18",sex:"女"};
const p2 = {...p1};
p1.name = "sss";
console.log(p2); //{name:"张三",age:"18",sex:"女"}
//复制时修改属性
const p1 = {name:"张三",age:"18",sex:"女"};
const p2 = {...p1,name : "111",hua:"ss"};
p1.name = "sss";
console.log(p2); //{name: "111", age: "18", sex: "女",hua:"ss"}
限制:
- 类型检查:propTypes
- 是否必填:propTypes
- 设默认值:defaultProps
//vue中的写法
export default {
name: 'MyCount',
// props: ['count', 'state'],
props: {
count: {
type: Number,
required: true,
default: 100
},
state: Boolean,
info: [String, Number]
},
}
//react
class Person extends React.Component {
render() {
return (
<ul>
<li>name: {this.props.name}</li>
<li>age: {this.props.age}</li>
<li>gender: {this.props.gender()}</li>
</ul>
);
}
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.string,
gender: PropTypes.func
}
static defaultProps = {
name: "Anonymity",
age: "18",
}
}
const getGender = () => { return "male"; }
ReactDOM.render(<Person gender={getGender}/>, document.getElementById("test"));
以上是在类式组件中使用props,下面是在函数式组件中使用props:
function Animal(props) {
return (
<ul>
<li>{props.name}</li>
<li>{props.age}</li>
<li>{props.gender}</li>
</ul>
)
}
const dog = { name: "Mike", age: 28, gender: "male" };
ReactDOM.render(<Animal {...dog}/>, document.getElementById("test2"));
(3).refs
①.字符串形式
官方不推荐,说后续版本可能移除这种方式
class Demo extends React.Component {
render() {
return (
<div>
<input ref="first" type="text" placeholder="Click button to get the input value"/>
<button onClick={this.getFirstValue}>Click</button>
<input onBlur={this.getSecondValue} ref="second" type="text" placeholder="Blur to get the input value"/>
</div>
)
}
getFirstValue = () => {
console.log(this.refs.first.value);
}
getSecondValue = () => {
console.log(this.refs.second.value);
}
}
ReactDOM.render(<Demo/>, document.getElementById("test"));
②.回调形式
将当前DOM元素赋给类自定义属性
class Demo extends React.Component {
render() {
return (
<div>
<input ref={ element => this.first = element } type="text" placeholder="Click button to get the input value" />
<button onClick={this.getFirstValue}>Click</button>
<input ref={element => this.second = element} onBlur={this.getSecondValue} type="text" placeholder="Blur to get the input value"/>
</div>
)
}
getFirstValue = () => {
console.log(this.first.value);
}
getSecondValue = () => {
console.log(this.second.value);
}
}
ReactDOM.render(<Demo />, document.getElementById("test2"));
回调形式有个特点:直接在DOM上写回调时,在重新渲染时,首次的element为null,第二次才是DOM元素。
可以改成从外面传回调。不过即使不这么做也没啥大问题。
class Demo extends React.Component {
state = { isHot: false }
getFirstRef = element => {
this.first = element;
console.log(element);
}
render() {
return (
<div>
<p>Today is {this.state.isHot ? 'hot' : 'cool'}.</p>
{/*<input ref={ element => {this.first = element; console.log(element)}} type="text" placeholder="Click button to get the input value" />*/}
<input ref={ this.getFirstRef } type="text" placeholder="Click button to get the input value" />
<button onClick={this.getFirstValue}>Click</button>
<input ref={ element => this.second = element } onBlur={this.getSecondValue} type="text" placeholder="Blur to get the input value" />
</div>
)
}
getFirstValue = () => {
console.log(this.first.value);
this.setState({ isHot: !this.state.isHot })
}
getSecondValue = () => {
console.log(this.second.value);
}
}
ReactDOM.render(<Demo />, document.getElementById("test1"));
③.API形式
React.createRef()创建React的容器,这个容器是专人专用的,所以每一个ref都需要创建这个。该API会将DOM元素赋值给实例对象的名称为容器名称的属性的current【这个current是固定的】
class Demo extends React.Component {
first = React.createRef();
second = React.createRef();
render() {
return (
<div>
<input ref={ this.first } type="text" placeholder="Click button to get the input value" />
<button onClick={this.getFirstValue}>Click</button>
<input ref={ this.second } onBlur={this.getSecondValue} type="text" placeholder="Blur to get the input value" />
</div>
)
}
getFirstValue = () => {
console.log(this.first.current.value);
}
getSecondValue = () => {
console.log(this.second.current.value);
}
}
ReactDOM.render(<Demo />, document.getElementById("test2"));
事件替代Ref
官方提示我们不要过度的使用ref,如果发生事件的元素刚好是需要操作的元素,就可以使用事件去替代。
class Demo extends React.Component {
first = React.createRef();
render() {
return (
<div>
<input ref={ this.first } type="text" placeholder="Click button to get the input value" />
<button onClick={this.getFirstValue}>Click</button>
<input onBlur={event => console.log(event.target.value)} type="text" placeholder="Blur to get the input value" />
</div>
)
}
getFirstValue = () => {
console.log(this.first.current.value);
}
}
ReactDOM.render(<Demo />, document.getElementById("test3"));
如果发生事件的元素不是需要操作的元素,也有办法
class Demo extends React.Component {
render() {
return (
<div>
<input onChange={ event => this.setState({ first: event.target.value }) } type="text" placeholder="Click button to get the input value" />
<button onClick={ () => console.log(this.state.first) }>Click</button>
<input onBlur={event => console.log(event.target.value)} type="text" placeholder="Blur to get the input value" />
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById("test4"));
3.受控组件与非受控组件
React中,表单元素(如<input>、 <textarea> 和 <select>)分为受控组件与非受控组件,我的理解:
- 受控组件:类似vue的双向绑定(推荐)(value不仅用户可以设置,还可由开发人员通过脚本设置)
- 非受控组件:只有 View => Model 的单项流动(value仅由用户设置)
// 非受控
class Login extends React.Component {
username = React.createRef();
password = React.createRef();
render() {
return (
<form action="" onSubmit={this.login}>
<label htmlFor="username">用户名</label>
<input type="text" name="username" id="username" ref={ this.username }/>
<br/>
<label htmlFor="password">密码</label>
<input type="password" name="password" id="password" ref={ this.password }/>
<button>登录</button>
</form>
)
}
login = (e) => {
e.preventDefault();
console.log('username: ', this.username.current.value, 'password: ', this.password.current.value);
}
}
ReactDOM.render(<Login/>, document.getElementById("test"));
// 受控:即时现场验证
class Login extends React.Component {
state = { username: '', password: '' }
render() {
return (
<form action="" onSubmit={this.login}>
<label htmlFor="username">用户名</label>
<input type="text" name="username" id="username"
value={this.state.username} onChange={this.checkUsername} />
<br />
<label htmlFor="password">密码</label>
<input type="password" name="password" id="password"
value={this.state.password} onChange={this.checkPassword} />
<button>登录</button>
</form>
)
}
checkUsername = (e) => {
const usernameRegex = /^[a-zA-Z0-9]{0,8}$/;
const flag = usernameRegex.test(e.target.value);
flag && (this.setState({ username: e.target.value }))
setTimeout(() => {
console.log(flag, this.state.username);
})
}
checkPassword = (e) => {
//长度在 8 到 20 个字符之间,必须包含至少一个大写字母、一个小写字母和一个数字
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{0,20}$/;
const flag = passwordRegex.test(e.target.value);
this.setState({ password: e.target.value });
setTimeout(() => {
console.log(flag, this.state.password);
})
}
login = (e) => {
e.preventDefault();
console.log('username: ', this.username.current.value, 'password: ', this.password.current.value);
}
}
ReactDOM.render(<Login />, document.getElementById("test2"));