参考的material design效果

最终效果

完整代码:
https://github.com/shengoo/HeaderTransition
react-native从0.50.1开始,提供了SafeAreaView来确保iPhone X的兼容性,效果如下:

代码如下:
import {
...
SafeAreaView
} from 'react-native';
class Main extends React.Component {
render() {
return (
<SafeAreaView style={styles.safeArea}>
<App />
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
...,
safeArea: {
flex: 1,
backgroundColor: '#ddd'
}
})
并且,SafeAreaView会在接打电话等需要调整状态栏高度的时候自动调整状态栏的高度:

$(selector)
$(selector,context)
$(Element|Document|Window)
$('<img/>')
$('<img/>',{
src: url
})
DOMContentLoaded
jQuery(function(){});
$(document).ready(function(){});
ES6的Proxy对象可以用来拦截一个Object对象的属性的修改,这次我们利用它来实现一个简单的双向绑定。
废话不多说,请看代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>View to model</title>
</head>
<body>
<div>
<!-- 使用'bind-to'的属性来标记这个输入框里的值会对Model里的属性进行修改 -->
<input bind-to="name" />
<input bind-to="name" />
</div>
<!-- 使用#属性名#来把Model里的值映射到DOM中 -->
<span>#name#</span>
<span>#name#</span>
<button onclick="reset()">reset</button>
<script type="text/javascript">
var handler = {
// 拦截属性的设置,触发试图的更新
set: function(target, key, value, receiver) {
target[key] = value;
updateView(propertyName);
return Reflect.set(target, key, value);
},
};
// 双向绑定的对象
var model = new Proxy({}, handler);
// 双向绑定列表
var inputs = {},views = {};
var all = document.all;
// 遍历所有DOM元素
for(var i=0,l=all.length;i<l;i++){
// 找到所有有bind-to的输入框
if(all[i].getAttribute('bind-to')){
var dom = all[i];
var propertyName = dom.getAttribute('bind-to');
inputs[propertyName] = inputs[propertyName] ? inputs[propertyName] : [];
views[propertyName] = views[propertyName] ? views[propertyName] : [];
inputs[propertyName].push(dom);
dom.addEventListener('change',function function_name(e) {
var propertyName = e.target.getAttribute('bind-to');
model[propertyName] = e.target.value;
})
}
// 找到所有model驱动的页面元素
if(all[i].innerHTML && all[i].innerHTML.length>2 &&
all[i].innerHTML[0] == '#' &&
all[i].innerHTML[all[i].innerHTML.length-1] == '#'){
var propertyName = all[i].innerHTML.slice(1,all[i].innerHTML.length-1);
inputs[propertyName] = inputs[propertyName] ? inputs[propertyName] : [];
views[propertyName] = views[propertyName] ? views[propertyName] : [];
views[propertyName].push(all[i]);
}
}
// 更新View
function updateView(propertyName) {console.log('update ' + propertyName);
if(views[propertyName]){
for(var i = 0,l = views[propertyName].length;i < l;i++){
views[propertyName][i].innerText = model[propertyName];
}
}
if(inputs[propertyName]){
for(var i = 0,l = inputs[propertyName].length;i < l;i++){
inputs[propertyName][i].value = model[propertyName];
}
}
}
function reset() {
for(var name in model){
model[name] = '';
}
}
</script>
</body>
</html>
点击右上角的“购买证书”按钮

找到免费的证书,阿里云的免费证书藏的比较深

选择品牌“Symantec”
证书类型里才会出现免费的,选择“免费型DV SSL”
购买并支付
打开证书管理页面
在证书列表里点击“补全”

填写域名信息
填写个人信息
提交
审核完成之后就可以在证书管理页面下载证书了

点击下载之后会打开下载证书的页面,选择对应的服务器的证书下载即可

在Nginx的安装目录下创建cert目录,并且将下载的全部文件拷贝到cert目录中。如果申请证书时是自己创建的CSR文件,请将对应的私钥文件放到cert目录下并且命名为214499409770626.key;
在/etc/nginx/sites-available里创建一个新的网站配置文件(以下属性中ssl开头的属性与证书配置有直接关系,其它属性请结合自己的实际情况复制或调整)
server {
listen 443;
server_name [你的域名];
ssl on;
root /var/www/html;
index index.php index.html index.htm index.nginx-debian.html;
ssl_certificate cert/[你的证书名字].pem;
ssl_certificate_key cert/[你的证书名字].key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html index.htm;
}
}
创建一个连接,把你刚才编辑的文件连接到/etc/nginx/sites-enabled目录下
重启 Nginx
通过 https 方式访问您的站点,测试站点证书的安装配置。
首先需要注册一个小程序的账号,然后才能创建微信小程序。
打开https://mp.weixin.qq.com/wxopen/waregister?action=step1,填写表单,提交成功并且验证邮件之后你就拥有了小程序的账号,可以创建小程序了。
注意:这里的邮箱不能是公众号的邮箱。
打开https://mp.weixin.qq.com/wxopen/initprofile,点击第一步最右边的连接,填写小程序的信息,就能创建小程序了。
打开https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html, 下载对应操作系统的开发工具。下载完成之后安装。

项目目录选择一个空的文件夹,app id填写你创建的app的id
输入项目名称
选择模板,我这里选择的是腾讯云Node.js模版
点击确定,即可使用模板创建一个小程序。

这个时候小程序可以运行了。
我们可以通过上传一个体验版,来测试手机上的效果
点击上传:

提示上传成功之后,点击预览即可生成一个二维码,用微信扫一扫就能看到体验版的小程序。

注意:这个二维码只有https://mp.weixin.qq.com/wxopen/authprofile这个页面添加过的成员,并且有体验者权限的用户才能打开预览。
效果如下:

选择“智能上传”并且勾选“部署后自动安装依赖”

等待部署完成

点击测试登录接口

说明我们的后端代码部署成功了。
这样就创建了一个最简单的小程序。
git resetgit add过的文件
git checkout .
撤回没提交的更改,要在repo的根目录运行
git reset --hard HEAD
也可以撤回没提交的更改,可以在任意的子目录中运行
git clean -fdx
删除untracked文件
git reset --hard origin/master
回到过去

react-native init rndd
./app/components/Draggable.jsimport React, {
Component,
} from 'react';
import {
StyleSheet,
Image,
PanResponder,
Animated,
} from 'react-native';
class Draggable extends Component{
constructor(props){
super(props);
}
render(){
return (
<Animated.View style={styles.container}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
}
export default Draggable;
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 100,
top: 100,
}
});
PanResponder的api可以上官网查看PanResponder
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (e, gestureState) => {
},
onPanResponderMove: Animated.event([
]),
onPanResponderRelease: (e, {vx, vy}) => {
}
});
}
render(){
return (
<Animated.View style={styles.container} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
先在state中创建一个对象来记录拖拽的记录。
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY()
};
}
更新panHandler:
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
// 设置初始位置
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setValue({x: 0, y: 0});
},
// 使用拖拽的偏移量来定位
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
}
});
}
更新render方法
render(){
// 从state中取出pan
const { pan } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
这个时候我们刷新用CMD+R刷新,发现已经可以拖动这个图片了,但是有个问题,第二次拖拽的时候又从原点开始移动,我们接下来解决这个问题。
我们有几个选择:
onPanResponderRelease: (e, {vx, vy}) => {
Animated.spring(
this.state.pan,
{toValue: {x: 0, y: 0}}
).start();
}
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
// 设置初始位置
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({
x: this.state.pan.x._value,
y: this.state.pan.y._value
});
this.state.pan.setValue({x: 0, y: 0});
},
// 使用拖拽的偏移量来定位
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
}
});
先在state里加上一个scale用来记录放大的倍数
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1)
};
我们要在render里使用放大的值
render(){
// 从state中取出pan和scale
const { pan, scale } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}, {scale}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
在拖拽的时候变大:
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({
x: this.state.pan.x._value,
y: this.state.pan.y._value
});
this.state.pan.setValue({x: 0, y: 0});
Animated.spring(
this.state.scale,
{ toValue: 1.3, friction: 3 }
).start();
},
在释放的时候缩小到原来的样子:
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
Animated.spring(
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
}
先在state中加上一个rotate用来记录旋转的角度
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
rotate: new Animated.Value(0)
};
在拖拽的时候旋转,需要在onPanResponderGrant里加上
Animated.timing(this.state.rotate, {
toValue: 25, // 旋转25%,render里interpolate出deg的值
duration: 300
}).start();
在释放的时候恢复原状,需要在onPanResponderRelease中加上
Animated.timing(this.state.rotate, {
toValue: 0,
duration: 300
}).start();
render函数里设置transform:
render(){
// 从state中取出pan
const { pan, scale } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 计算旋转
const rotate = this.state.rotate.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg']
});
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}, {scale}, {rotate}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
https://github.com/shengoo/rndd
import React, {
Component,
} from 'react';
import {
StyleSheet,
Image,
PanResponder,
Animated,
} from 'react-native';
class Draggable extends Component{
constructor(props){
super(props);
this.state = {
pan: new Animated.ValueXY(),
scale: new Animated.Value(1),
rotate: new Animated.Value(0)
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponderCapture: () => true,
// 设置初始位置
onPanResponderGrant: (e, gestureState) => {
this.state.pan.setOffset({
x: this.state.pan.x._value,
y: this.state.pan.y._value
});
this.state.pan.setValue({x: 0, y: 0});
Animated.spring(this.state.scale, {
toValue: 1.3,
friction: 3 }
).start();
Animated.timing(this.state.rotate, {
toValue: 25,
duration: 300
}).start();
},
// 使用拖拽的偏移量来定位
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset();
// Animated.spring(
// this.state.pan,
// {toValue: {x: 0, y: 0}}
// ).start();
Animated.spring(
this.state.scale,
{ toValue: 1, friction: 3 }
).start();
Animated.timing(this.state.rotate, {
toValue: 0,
duration: 300
}).start();
}
});
}
render(){
// 从state中取出pan
const { pan, scale } = this.state;
// 从pan里计算出偏移量
const [translateX, translateY] = [pan.x, pan.y];
// 计算旋转
const rotate = this.state.rotate.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg']
});
// 设置transform为偏移量
const imageStyle = {transform: [{translateX}, {translateY}, {scale}, {rotate}]};
return (
<Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
<Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
</Animated.View>
)
}
}
export default Draggable;
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 100,
top: 100,
}
});
n to manage your Node.js versionsnpm:(Recommended)npm install -g n
Since n‘s default path is /usr/local/n, it will need super user’s permission to modify file systems in the default path, we need to config n‘s path to user’s path.
N_PREFIXvim ~/.bashrc
export N_PREFIX=~/.n
source ~/.bashrc
vim ~/.bashrc
export PATH=$HOME/.n/bin:$PATH
source ~/.bashrc
# Use or install a version of node
n 9.0.0
# Use or install the latest official release
n latest
# Use or install the stable official release
n stable
# Use or install the latest LTS official release
n lts
Type n to show list of versions.
And select a version by up down button.
$ n
node/8.9.3
node/9.0.0
node/9.2.1
ο node/9.3.0
React的思想里,UI=render(data)
所以,React的组件一般是完成两种功能:
如果我们把两个功能都放在同一个组件内,那么这个组件做的事情就太多了,考虑到组件复用和业务变更,让一个组件只专注做一件事,我们可以把这个组件拆分成多个组件,让每个组件只专注做一件事,所以就衍生了一种React常用的模式:容器组件和展示组件。
容器组件在外层,有以下特点:
展示组件在容器组件内部,有以下特点:
展示组件一般可以使用纯函数的声明方式:
export default (props)=>(
<div>Hello {props.name}</div>
);