React 从入门到入门
这篇文章讲了 React 的基础知识,从 JSX 语法到面向组件编程:组件的属性 state,props,refs等属性
这是初学者写下的笔记,如有错误,欢迎指正!
- React:JavaScript库 (和 jQuery 一样是个库)
- 和 Vue 一样都尽量减少 DOM 的操作
- Facebook 出品的前端框架
How would you React if I said I love Vue?
React HelloWorld
<div class="test"></div>
<script type="text/babel">
// 创建虚拟 DOM
var vdom = <h1>Hello React</h1>;
// 将虚拟 DOM 渲染到页面
ReactDOM.render(vdom, document.querySelector('.test'));
</script>
-
刚上来的时候就给我来了一个报错,弄得我很懵,其实是内嵌 script 中 type 属性 应该为
type="text/babel"
否则会报一个出现意外的小于号的错(也就是无法解析 script 里面写的其他标签) -
react.js:React 的核心库
-
react-dom.js:提供操作 DOM 的 react 扩展库
-
babel.min.js:解析 jsx => js代码 (将 ES6 语法转换成 ES5)
虚拟 DOM
- React 创建虚拟 DOM 对象:
var myTitle = 'noobMing';
var element = React.createElement('h1',{id:myTitle},myTitle);
// 这种方法需要 babel 标签
const vDom = <h1 id={myTitle}>{myTitle}</h1>
- 创建好的元素:
<h1 id="noobMing">noobMing</h1>
大写转换成小写:
.toLowerCase()
小写转换成大写:.toUpperCase()
debugger
关键字:相当于在书写处添加了一个断点
JSX
-
全称
JavaScript XML
-
和 XML 一样都可以自定义标签 (之后叫组件标签)
-
babel.js 将 JSX 代码转换成纯 js 代码
-
假如有可能出现嵌套的 HTML 结构那么最好用括号括起来
-
如果我想用 li 渲染整个列表,那么我可以这么做:
let names = ['jquery', 'react', 'vue', 'angular'];
// 例如:
const ul = (<ul>
{names.map((name,index)=>{return <li key={index}>{name}</li>})}
</ul>)
// 渲染到页面
ReactDOM.render(ul, document.querySelector('.test'));
这就是虚拟 DOM 吗,我开始的时候想遍历整个列表,然后把他们边包裹成 li 边放到创建好的 ul 里面去,结果他不是在原来的 li 后面接着添加,而是直接覆盖掉前一个,又费事有没达到效果,看了老师的操作我想说还可以这么弄
- 和 Vue 小程序 一样,React 强制要求循环需要一个 key 属性来确保这个属性是唯一的
面向组件编程
- 组件标签首字母要大写
- 分成两步:1.定义组件,2.渲染组件标签
- 和 vue 的组件一样,最外层都要有一个父元素做包裹
- 定义组件一共有三种方式:
// 1. 工厂函数组件 (简单组件)
function MyComponent() {
return <h2></h2>;
}
// 渲染标签
ReactDOM.render(<MyComponent />,document.querySelector('div'));
- 在真实 DOM 中,浏览器会将组件的最外层标签给去掉,只留下组件内部的标签
// ES6 的类组件 (复杂组件)
class MyComponent2 extends React.Component {
render() {
return <h2>ES6 的类组件 (复杂组件)</h2>
}
}
- 在这里输出的 this 指向组件对象
组件的状态:state
- state 叫做状态机,值是对象,通过更新组件的 state 来更新组件的显示
- 下面这段代码是你点击一下标签内文字就改变
class MyComponent2 extends React.Component {
constructor(props) {
// 调用父类型的构造函数
super(props);
// 初始化状态
this.state = {
isLikeMe: false
}
// 将新增方法中的 this 强制绑定为组件对象 (后期可以通过箭头函数来更改方法中 this 的指向)
this.handleClick = this.handleClick.bind(this)
}
// 重写组件类的渲染方法
render() {
let { isLikeMe } = this.state;
return <h2 onClick={this.handleClick}>{isLikeMe ? 'How would you React if I said I love Vue?' : "tip"}</h2>
}
// 新添加的方法:内部的 this 默认不是组件对象,而是 undefined
handleClick() {
// 得到原来的状态
// this.state.isLikeMe
// 更新状态
this.setState({
isLikeMe: !this.state.isLikeMe
})
}
}
- 这里又和小程序有点像,想要改变 state 状态的话需要
this.setState()
方法 - 只要你的组件有状态 (state),就不可以用工程模式 (只用函数创造组件的模式)
组件的传值:props
- props 是只读的属性
- 假如我想在不同的组件上渲染不同的值
function Person1(props) {
return (<ul>
<li>{props.name}</li>
<li>{props.age}</li>
<li>{props.gender}</li>
</ul>)
}
// 假设这是从后台传过来的值
let p1 = {
name: "noobMing",
age: 18,
gender: "man"
}
ReactDOM.render(<Person1 name={p1.name} age={p1.age} gender={p1.gender} />, document.querySelector('.person'));
- 或者我想写成类组件:
class Person2 extends React.Component {
render() {
return (<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.gender}</li>
</ul>)
}
}
// 注意传值的时候里面要加 this
- 感觉这个 props 起到了一个传参的作用
- props 还可以设置默认值:
// 例如我有一个 Person 组件
Person.defaultProps = {
gender: "man"
}
- 这里我们设置了 gender 的默认属性为 man
- 我们想让某个值是必须的 (例如名字是必填项) 或者对名字进行判断
- 需要引入 prop-types.min.js 这个文件
// 还是那个 Person 组件
Person.propTypes = {
// 说明这个属性应该是 string 类型,并且是必须的
name: propTypes.string.isRequired
}
- 渲染部分的 js
ReactDOM.render(<Person name={p1.name} age={p1.age} gender={p1.gender} />, document.querySelector('.person'));
- 里面这部分的写法下面两种是一样的:
// 定义一个对象 p1 把他的值传给组件
name={p1.name} age={p1.age} gender={p1.gender}
{...p1}
- 但是我在写这里的时候发生了一个问题:刚开始的时候提示没有引入 propTypes ,当我引入 propTypes 后他会在引入的那行报错,说 require 未定义
- 老师在视频中没有引入也可以使用 propTypes 我即使引入也会报错,但是我发现了问题所在,引入的语句属于 ES6 语法,所以 babel 会将其转化为 ES5 语法,而不知道为什么浏览器没有找到 require 函数的定义,所以才会报错
// 转换前 (ES6) 和转换后 (ES5) 的引入 propTypes 方法
import PropTypes from 'prop-types';
var PropTypes = require('prop-types');
- 感觉 props 可以传递各种参数,需要传参的时候只用输出 props 然后在里面要找的参数就可以了
组件的属性:refs
- ref 主要是标识组件内部的某一个元素
- 感觉 ref 就是一个特殊的属性,它能把组件内的标签绑定到组件对象上,方便以后进行操作
// ref 的实例:绑定 input 里面的值
class Test extends React.Component {
constructor(props) {
super(props);
// 修改 this
this.showInput = this.showInput.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
showInput() {
console.log(this.input.value);
}
handleBlur(event) {
// 这两种写法都能找到 input 里面的值
alert(this.input2.value);
alert(event.target.value);
}
render() {
return (
<div>
// 官方建议的写法 (把当前的 input 元素绑定到组件对象上去了)
<input type="text" ref={input => this.input = input} />
<button onClick={this.showInput}>提示</button>
<input type="text" onBlur={this.handleBlur} ref={input => this.input2 = input} />
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector('.base'));
- 还有一种废弃掉的写法:
// 组件的元素
<input type="text" ref="content" />
// 调用的方法
showInput() {
// 在函数中找到组件标签
const input = this.refs.content;
alert(input.value);
}
组件的嵌套
注意:vscode 自动生成的 input 标签里面是没有终止符号
/
的,但是在 JSX 里面没有终止符号就会报错
-
在使用的时候经常会出现大组件套小组件的情况,这时就需要组件的嵌套
-
实例:要实现一个 todo list 页面,需要一个 App 大组件,里面嵌套两个小组件 MyInput 和 MyList 组件分别进行输入和显示
-
这里面的一个问题就是 MyInput 组件获取到用户输入之后如何把内容给 MyList 组件
-
这里我们把信息储存在他们的上一级组件之中 (App),然后把更新数据的函数当作参数传给 MyInput 组件,这样在子组件里就可以更新上一级组件的里面的信息,然后再把这个数据作为参数传给 MyList 组件,MyList 组件渲染这个列表就有了最简单的 todo 应用的效果
-
在子组件中更新父组件里面元素的状态:状态在那个组件,更新状态的函数就应该在哪个组件
-
代码:
// App 组件
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
// 用 state 储存输入的内容
todos: []
}
this.addtodos = this.addtodos.bind(this);
}
// 修改
addtodos(todo) {
// 获取整个列表
let { todos } = this.state;
// 在开头添加数据
todos.unshift(todo);
// 渲染回去
this.setState({ todos });
// 不可以这么做:this.state.todos.unshift(todo);
}
render() {
return (<div>
<h1>ToDoList</h1>
// 把函数当作参数传到子组件上
<MyInput addtodos={this.addtodos} />
<MyList todos={this.state.todos} />
</div>);
}
}
- MyInput 组件:
class MyInput extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 设置 button 上的文字
this.setState({
counter: ++this.state.counter
})
this.button.innerHTML = "Add #" + this.state.counter;
// 调用接收过来的函数
this.props.addtodos(this.input.value);
// 清空输入框
this.input.value = null;
}
render() {
return (<div>
<input type="text" ref={input => this.input = input} />
<button onClick={this.handleClick} ref={button => this.button = button}>Add #0</button>
</div>)
}
}
-
注意:子组件接收参数是在 props 属性里面接收的
-
MyList 组件:
class MyList extends React.Component {
render() {
console.log(this);
// 不得不说,React 渲染列表真的很方便
return (<ul>{this.props.todos.map((todo, index) => {
return <li key={index}>{todo}</li>
})}</ul>)
}
}
// 渲染 App 组件到页面上
ReactDOM.render(<App />, document.querySelector('.innerInput'));
- 组件化编写功能的流程
- 拆分组件
- 实现静态组件(只有静态界面,没有动态数据和交互)
- 实现动态组件
1). 实现初始化数据动态显示
2). 实现交互功能
在组件中收集表单数据
-
阻止默认行为:
event.preventDefault()
-
原生的 onchange 事件是在失去焦点的时候触发,而 React 中的 onChange 事件是在输入的时候就触发事件
-
取出表单中数据有两种方法:使用 ref 在组件对象上绑定好元素;或者使用 state 属性让输入框内容的值 (value) 等于在 state 中定义的变量,然后监听输入框改变的事件,每在输入框中输入都会更新 state 中的数据 (推荐第二种)
-
实例:两个输入框分别用 ref 和 state 绑定数据,点击 login 按钮时弹出输入的用户名和密码
class MyCompent extends React.Component {
constructor(props) {
super(props);
this.state = {
pwd: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(event) {
// 阻止默认行为
event.preventDefault();
console.log(this.nameInput);
alert("用户名" + this.nameInput.value + "密码" + this.state.pwd);
}
handleChange(event) {
// 让输入框和 state 中的数据同步
let pwd = event.target.value;
this.setState({ pwd });
}
render() {
return (<form action="" onSubmit={this.handleSubmit}>
<label for="name">name:</label>
// 使用 ref 将整个元素绑定到组件对象上
<input type="text" id="name" ref={input => this.nameInput = input} />
<label for="password" >password:</label>
// 在 state 中绑定数据,并且绑定监听输入框发生改变事件 handleChange
<input type="password" id="password" value={this.state.pwd} onChange={this.handleChange} />
<input type="submit" value="login" />
</form>)
}
}
ReactDOM.render(<MyCompent />, document.querySelector(".form"));
- 受控组件:表单项输入数据能自动收集成状态 (每输入一个数,后台自动更新输入的数据:state 方法)
- 非受控组件:需要时才手动读取表单输入框中的数据 (点击提交按钮的时候才更新数据:ref 方法)
- 官网不推荐使用 refs 推荐使用 state